use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.
the class Appointment method processPartStat.
@Override
protected String processPartStat(Invite invite, MimeMessage mmInv, boolean forCreate, String defaultPartStat) throws ServiceException {
Mailbox mbox = getMailbox();
OperationContext octxt = mbox.getOperationContext();
CreateCalendarItemPlayer player = octxt != null ? (CreateCalendarItemPlayer) octxt.getPlayer() : null;
long opTime = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
Account account = getMailbox().getAccount();
boolean onBehalfOf = false;
Account authAcct = account;
if (octxt != null) {
Account authuser = octxt.getAuthenticatedUser();
if (authuser != null) {
onBehalfOf = !account.getId().equalsIgnoreCase(authuser.getId());
if (onBehalfOf)
authAcct = authuser;
}
}
boolean asAdmin = octxt != null ? octxt.isUsingAdminPrivileges() : false;
boolean allowPrivateAccess = allowPrivateAccess(authAcct, asAdmin);
String partStat = defaultPartStat;
if (player != null) {
String p = player.getCalendarItemPartStat();
if (p != null)
partStat = p;
}
// See if we have RSVP=FALSE for the attendee. Let's assume RSVP was requested unless it is
// explicitly set to FALSE.
boolean rsvpRequested = true;
ZAttendee attendee = invite.getMatchingAttendee(account);
if (attendee != null) {
Boolean rsvp = attendee.getRsvp();
if (rsvp != null)
rsvpRequested = rsvp.booleanValue();
}
RedoLogProvider redoProvider = RedoLogProvider.getInstance();
// Don't send reply emails if we're not on master (in redo-driven master/replica setup).
// Don't send reply emails if we're replaying redo for reasons other than crash recovery.
// In other words, we DO want to send emails during crash recovery, because we're completing
// an interrupted transaction. But we don't send emails during redo reply phase of restore.
// Also don't send emails for cancel invites. (Organizer doesn't expect reply for cancels.)
// And don't send emails for task requests.
// Don't send reply emails from a system account. (e.g. archiving, galsync, ham/spam)
boolean needReplyEmail = rsvpRequested && redoProvider.isMaster() && (player == null || redoProvider.getRedoLogManager().getInCrashRecovery()) && invite.hasOrganizer() && !invite.isCancel() && !invite.isTodo() && !account.isIsSystemResource();
if (invite.isOrganizer()) {
// Organizer always accepts.
partStat = IcalXmlStrMap.PARTSTAT_ACCEPTED;
} else if (account instanceof CalendarResource && octxt == null) {
// Auto accept/decline processing should only occur during email delivery. In particular,
// don't do it if we're here during ics import. We're in email delivery if octxt == null.
// (There needs to be a better way to determine that...)
boolean replySent = false;
CalendarResource resource = (CalendarResource) account;
Locale lc;
Account organizer = invite.getOrganizerAccount();
if (organizer != null)
lc = organizer.getLocale();
else
lc = resource.getLocale();
if (resource.autoAcceptDecline() || resource.autoDeclineIfBusy() || resource.autoDeclineRecurring()) {
boolean replyListUpdated = false;
// If auto-accept is enabled, assume it'll be accepted until it gets declined.
if (resource.autoAcceptDecline())
partStat = IcalXmlStrMap.PARTSTAT_ACCEPTED;
if (isRecurring() && resource.autoDeclineRecurring()) {
// Decline because resource is configured to decline all recurring appointments.
partStat = IcalXmlStrMap.PARTSTAT_DECLINED;
if (needReplyEmail) {
String reason = L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonRecurring, lc);
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_DECLINE);
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, false, reason + "\r\n", this, invite, new Invite[] { replyInv }, mmInv);
replySent = true;
}
} else if (resource.autoDeclineIfBusy()) {
// Auto decline is enabled. Let's check for conflicts.
int maxNumConflicts = resource.getMaxNumConflictsAllowed();
int maxPctConflicts = resource.getMaxPercentConflictsAllowed();
ConflictCheckResult checkResult = checkAvailability(opTime, invite, maxNumConflicts, maxPctConflicts);
if (checkResult != null) {
List<Conflict> conflicts = checkResult.getConflicts();
if (conflicts.size() > 0) {
if (invite.isRecurrence() && !checkResult.tooManyConflicts()) {
// There are some conflicts, but within resource's allowed limit.
if (resource.autoAcceptDecline()) {
// Let's accept partially. (Accept the series and decline conflicting instances.)
List<Invite> replyInvites = new ArrayList<Invite>();
// the REPLY for the ACCEPT of recurrence series
Invite acceptInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_ACCEPT);
for (Conflict conflict : conflicts) {
Instance inst = conflict.getInstance();
InviteInfo invInfo = inst.getInviteInfo();
Invite inv = getInvite(invInfo.getMsgId(), invInfo.getComponentId());
RecurId rid = inst.makeRecurId(inv);
// Record the decline status in reply list.
getReplyList().modifyPartStat(resource, rid, null, resource.getName(), null, null, IcalXmlStrMap.PARTSTAT_DECLINED, false, invite.getSeqNo(), opTime);
replyListUpdated = true;
// Make REPLY VEVENT for the declined instance.
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, inv, rid, CalendarMailSender.VERB_DECLINE);
replyInvites.add(replyInv);
}
if (needReplyEmail) {
ICalTimeZone tz = chooseReplyTZ(invite);
// Send one email to accept the series.
String declinedInstances = getDeclinedTimesString(octxt, mbox, conflicts, invite.isAllDayEvent(), tz, lc);
String msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclinedInstances, lc) + "\r\n\r\n" + declinedInstances;
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_ACCEPT, true, msg, this, invite, new Invite[] { acceptInv }, mmInv);
// Send another email to decline instances, all in one email.
String conflictingTimes = getBusyTimesString(octxt, mbox, conflicts, tz, lc, false);
msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclinedInstances, lc) + "\r\n\r\n" + declinedInstances + "\r\n" + L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonConflict, lc) + "\r\n\r\n" + conflictingTimes;
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, true, msg, this, invite, replyInvites.toArray(new Invite[0]), mmInv);
replySent = true;
}
} else {
// Auto-accept is not enabled. Auto-decline is enabled, but there weren't
// enough conflicting instances to decline outright. So we just stay
// silent and let the human admin deal with it. This case is rather
// ambiguous, and can be avoided by configuring the resource to allow
// zero conflicting instance.
}
} else {
// Too many conflicts. Decline outright.
partStat = IcalXmlStrMap.PARTSTAT_DECLINED;
if (needReplyEmail) {
ICalTimeZone tz = chooseReplyTZ(invite);
String msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonConflict, lc) + "\r\n\r\n" + getBusyTimesString(octxt, mbox, conflicts, tz, lc, checkResult.hasMoreConflicts());
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_DECLINE);
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, false, msg, this, invite, new Invite[] { replyInv }, mmInv);
replySent = true;
}
}
}
}
}
if (!replySent && IcalXmlStrMap.PARTSTAT_ACCEPTED.equals(partStat)) {
if (needReplyEmail) {
Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_ACCEPT);
CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_ACCEPT, false, null, this, invite, new Invite[] { replyInv }, mmInv);
}
}
// Record the final outcome in the replies list.
if (IcalXmlStrMap.PARTSTAT_NEEDS_ACTION.equals(partStat)) {
getReplyList().modifyPartStat(resource, invite.getRecurId(), null, resource.getName(), null, null, partStat, false, invite.getSeqNo(), opTime);
replyListUpdated = true;
}
if (forCreate && replyListUpdated)
saveMetadata();
}
}
CreateCalendarItemRecorder recorder = (CreateCalendarItemRecorder) mbox.getRedoRecorder();
recorder.setCalendarItemPartStat(partStat);
invite.updateMyPartStat(account, partStat);
if (forCreate) {
Invite defaultInvite = getDefaultInviteOrNull();
if (defaultInvite != null && !defaultInvite.equals(invite) && !partStat.equals(defaultInvite.getPartStat())) {
defaultInvite.updateMyPartStat(account, partStat);
saveMetadata();
}
}
return partStat;
}
use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.
the class CalendarItem method modifyPartStat.
/**
*
* Used when we're sending out a reply -- we add a "reply" record to this appointment/task...this
* ends up affecting our "Effective PartStat" (ie if we ACCEPT a meeting, then our effective partstat
* changes)
*
* @param acctOrNull
* @param recurId
* @param cnStr
* @param addressStr
* @param cutypeStr
* @param roleStr
* @param partStatStr
* @param rsvp
* @param seqNo
* @param dtStamp
* @throws ServiceException
*/
void modifyPartStat(Account acctOrNull, RecurId recurId, String cnStr, String addressStr, String cutypeStr, String roleStr, String partStatStr, Boolean needsReply, int seqNo, long dtStamp) throws ServiceException {
mReplyList.modifyPartStat(acctOrNull, recurId, cnStr, addressStr, cutypeStr, roleStr, partStatStr, needsReply, seqNo, dtStamp);
if (addressStr != null) {
Invite inv = getInvite(recurId);
if (inv != null) {
ZAttendee at;
if (acctOrNull != null)
at = inv.getMatchingAttendee(acctOrNull);
else
at = inv.getMatchingAttendee(addressStr);
if (at != null)
at.setPartStat(partStatStr);
}
}
saveMetadata();
}
use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.
the class CalendarItem method getIndexDocuments.
protected List<IndexDocument> getIndexDocuments() throws TemporaryIndexingException {
List<IndexDocument> toRet = new ArrayList<IndexDocument>();
// when this method is called during commit of cancel operation.
if (numInvites() < 1)
return toRet;
Invite defaultInvite = getDefaultInviteOrNull();
String defaultLocation = "";
if (defaultInvite != null && defaultInvite.getLocation() != null)
defaultLocation = defaultInvite.getLocation();
String defaultName = "";
if (defaultInvite != null && defaultInvite.getName() != null)
defaultName = defaultInvite.getName();
String defaultOrganizer = "";
if (defaultInvite != null && defaultInvite.getOrganizer() != null)
defaultOrganizer = defaultInvite.getOrganizer().getIndexString();
for (Invite inv : getInvites()) {
StringBuilder s = new StringBuilder();
List<String> toAddrs = new ArrayList<String>();
// NAME (subject)
String nameToUse = "";
if (inv.getName() != null) {
s.append(inv.getName()).append(' ');
nameToUse = inv.getName();
} else {
s.append(defaultName).append(' ');
nameToUse = defaultName;
}
// ORGANIZER (from)
String orgToUse = null;
if (inv.getOrganizer() != null) {
String thisInvOrg = inv.getOrganizer().getIndexString();
if (thisInvOrg != null && thisInvOrg.length() > 0)
orgToUse = thisInvOrg;
}
if (orgToUse == null)
orgToUse = defaultOrganizer;
// ATTENDIES (TO)
for (ZAttendee at : inv.getAttendees()) {
try {
toAddrs.add(at.getFriendlyAddress().toString());
s.append(at.getIndexString()).append(' ');
} catch (ServiceException e) {
}
}
s.append(' ');
// LOCATION
if (inv.getLocation() != null) {
s.append(inv.getLocation()).append(' ');
} else {
s.append(defaultLocation).append(' ');
}
// DESCRIPTION
try {
s.append(inv.getDescription()).append(' ');
} catch (ServiceException ex) {
if (ZimbraLog.index.isDebugEnabled()) {
ZimbraLog.index.debug("Caught exception fetching description while indexing CalendarItem " + this.getId() + " skipping", ex);
}
}
// COMMENTS
List<String> comments = inv.getComments();
if (comments != null && !comments.isEmpty()) {
for (String comm : comments) {
s.append(comm).append(' ');
}
}
// CONTACTS
List<String> contacts = inv.getContacts();
if (contacts != null && !contacts.isEmpty()) {
for (String contact : contacts) {
s.append(contact).append(' ');
}
}
// CATEGORIES
List<String> categories = inv.getCategories();
if (categories != null && !categories.isEmpty()) {
for (String cat : categories) {
s.append(cat).append(' ');
}
}
MimeMessage mm = null;
if (!inv.getDontIndexMimeMessage()) {
try {
mm = inv.getMimeMessage();
} catch (ServiceException e) {
if (ZimbraLog.index.isDebugEnabled()) {
ZimbraLog.index.debug("Caught MessagingException for Invite " + inv.toString() + " while fetching MM during indexing of CalendarItem " + this.getId() + " skipping Invite", e);
}
}
}
List<IndexDocument> docList = new ArrayList<IndexDocument>();
if (mm == null) {
// no blob!
IndexDocument doc = new IndexDocument();
// need to properly emulate an indexed Invite message here -- set the TOP partname
doc.addPartName(LuceneFields.L_PARTNAME_TOP);
docList.add(doc);
} else {
try {
ParsedMessage pm = new ParsedMessage(mm, mMailbox.attachmentsIndexingEnabled());
pm.analyzeFully();
if (pm.hasTemporaryAnalysisFailure())
throw new MailItem.TemporaryIndexingException();
docList = pm.getLuceneDocuments();
} catch (ServiceException e) {
if (ZimbraLog.index.isDebugEnabled()) {
ZimbraLog.index.debug("Caught MessagingException for Invite " + inv.toString() + " while indexing CalendarItem " + this.getId() + " skipping Invite", e);
}
}
}
for (IndexDocument doc : docList) {
// update the doc, overriding many of the fields with data from the appointment
doc.addContent(s.toString());
doc.removeTo();
doc.removeFrom();
doc.removeSubject();
for (String to : toAddrs) {
doc.addTo(new RFC822AddressTokenStream(to));
}
doc.addFrom(new RFC822AddressTokenStream(orgToUse));
doc.addSubject(nameToUse);
toRet.add(doc);
}
}
// set the "public"/"private" flag in the index for this appointment
FieldTokenStream fields = new FieldTokenStream(INDEX_FIELD_ITEM_CLASS, isPublic() ? "public" : "private");
for (IndexDocument doc : toRet) {
doc.addField(fields);
}
return toRet;
}
use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.
the class CalendarItem method delete.
@Override
void delete(boolean writeTombstones) throws ServiceException {
Invite defInv = getDefaultInviteOrNull();
String sbj;
if (defInv != null)
sbj = defInv.isPublic() ? defInv.getName() : "(private)";
else
sbj = "(none)";
ZimbraLog.calendar.info("Deleting CalendarItem: id=%d, folderId=%d, subject=\"%s\", UID=%s", mId, getFolderId(), sbj, mUid);
if (!isPublic() && !canAccess(ACL.RIGHT_PRIVATE))
throw ServiceException.PERM_DENIED("you do not have permission to delete private calendar item from the current folder");
super.delete(writeTombstones);
}
use of com.zimbra.cs.mailbox.calendar.Invite in project zm-mailbox by Zimbra.
the class CalendarItem method expandInstances.
/**
* Expand all the instances for the time period from start to end
*
* @param start
* @param end
* @param includeAlarmOnlyInstances
* @return list of Instances for the specified time period
*/
public Collection<Instance> expandInstances(long start, long end, boolean includeAlarmOnlyInstances) throws ServiceException {
long endAdjusted = end;
long alarmInstStart = 0;
if (includeAlarmOnlyInstances) {
// range.
if (mAlarmData != null) {
alarmInstStart = mAlarmData.getNextInstanceStart();
long nextAlarm = mAlarmData.getNextAtBase();
if (nextAlarm >= start && nextAlarm < end) {
if (alarmInstStart >= end)
endAdjusted = alarmInstStart + 1;
}
}
}
List<Instance> instances = new ArrayList<Instance>();
if (mRecurrence != null) {
long startTime = System.currentTimeMillis();
instances = Recurrence.expandInstances(mRecurrence, getId(), start, endAdjusted);
if (ZimbraLog.calendar.isDebugEnabled()) {
long elapsed = System.currentTimeMillis() - startTime;
ZimbraLog.calendar.debug("RECURRENCE EXPANSION for appt/task %s: start=%s, end=%s; took %sms. %s instances", getId(), start, end, elapsed, instances.size());
}
} else {
// organizer.
if (mInvites != null) {
for (Invite inv : mInvites) {
if (// Skip canceled instances.
inv.isCancel())
continue;
ParsedDateTime dtStart = inv.getStartTime();
long invStart = dtStart != null ? dtStart.getUtcTime() : 0;
ParsedDateTime dtEnd = inv.getEffectiveEndTime();
long invEnd = dtEnd != null ? dtEnd.getUtcTime() : 0;
if ((invStart < endAdjusted && invEnd > start) || (dtStart == null)) {
Instance inst = new Instance(getId(), new InviteInfo(inv), dtStart != null, dtEnd != null, invStart, invEnd, inv.isAllDayEvent(), dtStart != null ? dtStart.getOffset() : 0, dtEnd != null ? dtEnd.getOffset() : 0, inv.hasRecurId(), false);
instances.add(inst);
}
}
}
}
// Remove instances that aren't in the actual range.
for (Iterator<Instance> iter = instances.iterator(); iter.hasNext(); ) {
Instance inst = iter.next();
if (inst.hasStart() && inst.hasEnd()) {
long instStart = inst.getStart();
long instEnd = inst.getEnd();
// or instance starts after range end. (i.e. instance does not overlap range)
if (instStart != alarmInstStart && (instEnd <= start || instStart >= end))
iter.remove();
}
}
return instances;
}
Aggregations