use of com.zimbra.cs.mailbox.calendar.InviteInfo 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.InviteInfo 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;
}
use of com.zimbra.cs.mailbox.calendar.InviteInfo in project zm-mailbox by Zimbra.
the class CalendarItem method updateRecurrence.
private boolean updateRecurrence(long nextAlarm) throws ServiceException {
long startTime, endTime;
// update our recurrence rule, start with the initial rule
Invite firstInv = getDefaultInviteOrNull();
if (firstInv == null) {
return false;
}
IRecurrence recur = firstInv.getRecurrence();
if (recur instanceof Recurrence.RecurrenceRule) {
mRecurrence = (IRecurrence) recur.clone();
// now, go through the list of invites and find all the exceptions
for (Invite cur : mInvites) {
if (cur != firstInv) {
String method = cur.getMethod();
if (cur.isCancel()) {
assert (cur.hasRecurId());
if (cur.hasRecurId()) {
checkExdateIsSensible(cur.getRecurId());
Recurrence.CancellationRule cancelRule = new Recurrence.CancellationRule(cur.getRecurId());
((Recurrence.RecurrenceRule) mRecurrence).addException(cancelRule);
}
} else if (method.equals(ICalTok.REQUEST.toString()) || method.equals(ICalTok.PUBLISH.toString())) {
assert (cur.hasRecurId());
if (cur.hasRecurId() && cur.getStartTime() != null) {
checkRecurIdIsSensible(cur.getRecurId());
Recurrence.ExceptionRule exceptRule = null;
IRecurrence curRule = cur.getRecurrence();
if (curRule != null && curRule instanceof Recurrence.ExceptionRule) {
exceptRule = (Recurrence.ExceptionRule) curRule.clone();
} else {
// create a fake ExceptionRule wrapper around the single-instance
exceptRule = new Recurrence.ExceptionRule(cur.getRecurId(), cur.getStartTime(), cur.getEffectiveDuration(), new InviteInfo(cur));
}
((Recurrence.RecurrenceRule) mRecurrence).addException(exceptRule);
} else {
sLog.debug("Got second invite with no RecurID: " + cur.toString());
}
}
}
}
// Find the earliest DTSTART and latest DTEND. We're just looking for the bounds, so we won't worry
// about cancelled instances.
ParsedDateTime earliestStart = null;
ParsedDateTime latestEnd = null;
for (Invite cur : mInvites) {
if (!cur.isCancel()) {
ParsedDateTime start = cur.getStartTime();
if (earliestStart == null)
earliestStart = start;
else if (start != null && start.compareTo(earliestStart) < 0)
earliestStart = start;
ParsedDateTime end = cur.getEffectiveEndTime();
if (latestEnd == null)
latestEnd = end;
else if (end != null && end.compareTo(latestEnd) > 0)
latestEnd = end;
}
}
// Take the later of latestEnd and recurrence's end time.
ParsedDateTime recurEnd = mRecurrence.getEndTime();
if (latestEnd == null)
latestEnd = recurEnd;
else if (recurEnd != null && recurEnd.compareTo(latestEnd) > 0)
latestEnd = recurEnd;
// update the start and end time in the CalendarItem table if
// necessary
startTime = earliestStart != null ? earliestStart.getUtcTime() : 0;
endTime = latestEnd != null ? latestEnd.getUtcTime() : 0;
} else {
mRecurrence = null;
startTime = 0;
endTime = 0;
for (Invite inv : mInvites) {
if (!inv.isCancel()) {
ParsedDateTime dtStart = inv.getStartTime();
long st = dtStart != null ? dtStart.getUtcTime() : 0;
if (st != 0 && (st < startTime || startTime == 0))
startTime = st;
ParsedDateTime dtEnd = inv.getEffectiveEndTime();
long et = dtEnd != null ? dtEnd.getUtcTime() : 0;
if (et != 0 && et > endTime)
endTime = et;
}
}
}
// Adjust start/end times before recomputing alarm because alarm computation depends on those times.
boolean timesChanged = false;
if (mStartTime != startTime || mEndTime != endTime) {
timesChanged = true;
mStartTime = startTime;
mEndTime = endTime;
}
// Recompute next alarm. Bring appointment start time forward to the alarm time,
// if the next alarm is before the first instance.
recomputeNextAlarm(nextAlarm, false, false);
if (mAlarmData != null) {
long newNextAlarm = mAlarmData.getNextAtBase();
if (newNextAlarm > 0 && newNextAlarm < startTime && mStartTime != startTime) {
timesChanged = true;
mStartTime = newNextAlarm;
}
}
if (timesChanged) {
if (ZimbraLog.calendar.isDebugEnabled()) {
ZimbraLog.calendar.debug("Updating recurrence for %s. nextAlarm=%d.", getMailopContext(this), nextAlarm);
}
DbMailItem.updateInCalendarItemTable(this);
}
return true;
}
use of com.zimbra.cs.mailbox.calendar.InviteInfo in project zm-mailbox by Zimbra.
the class CalendarItem method getNextAlarm.
private AlarmData getNextAlarm(long nextAlarm, boolean skipAlarmDefChangeCheck, AlarmData currentNextAlarmData, boolean dismissed, boolean forEmailAction) throws ServiceException {
if (nextAlarm == NEXT_ALARM_ALL_DISMISSED || !hasAlarm()) {
return null;
}
long now = getMailbox().getOperationTimestampMillis();
// Chosen alarm must be at or after this time.
long atOrAfter;
long snoozeUntil;
if (nextAlarm == NEXT_ALARM_KEEP_CURRENT) {
// special case to preserve current next alarm trigger time
if (currentNextAlarmData != null) {
atOrAfter = currentNextAlarmData.getNextAtBase();
snoozeUntil = currentNextAlarmData.getSnoozeUntil();
} else {
// no existing alarm; pick the first alarm in the future
atOrAfter = snoozeUntil = now;
}
} else if (nextAlarm == NEXT_ALARM_FROM_NOW) {
// another special case to mean starting from "now"; pick the first alarm in the future
atOrAfter = snoozeUntil = now;
} else if (!dismissed && currentNextAlarmData != null) {
// if not dismissing previous alarm, keep it as the base trigger time. nextAlarm has snoozed re-trigger time
atOrAfter = currentNextAlarmData.getNextAtBase();
snoozeUntil = nextAlarm;
} else {
// else we just use the nextAlarm value that was passed in
atOrAfter = snoozeUntil = nextAlarm;
}
if (atOrAfter <= 0) {
// sanity check
atOrAfter = snoozeUntil = now;
}
if (snoozeUntil != AlarmData.NO_SNOOZE && snoozeUntil < atOrAfter) {
snoozeUntil = atOrAfter;
}
// startTime and endTime limit the time range for meeting instances to be examined.
// All instances that ended before startTime are ignored, and by extension the alarms for them.
// endTime limit is set to avoid examining too many instances, for performance reason.
long startTime = atOrAfter;
if (startTime > now) {
// Handle the case when appointment is brought back in time such that the new start time
// is earlier than previously set alarm trigger time.
startTime = now;
}
long endTime = getNextAlarmRecurrenceExpansionLimit();
Collection<Instance> instances = expandInstances(startTime, endTime, false);
// Special handling for modified alarm definition
if (atOrAfter > 0 && !skipAlarmDefChangeCheck) {
// Let's see if alarm definition has changed. It changed if there is no alarm to go off at
// previously saved nextAlarm time.
boolean alarmDefChanged = true;
long savedNextInstStart = currentNextAlarmData != null ? currentNextAlarmData.getNextInstanceStart() : 0;
for (Instance inst : instances) {
long instStart = inst.getStart();
long instEnd = inst.getEnd();
if (inst.hasStart() && inst.hasEnd()) {
// Ignore instances that ended already.
if (instEnd <= startTime)
continue;
// For appointments (but not tasks), ignore instances whose start time has come and gone.
if (instStart < startTime && (this instanceof Appointment))
continue;
// definition.
if (instStart > savedNextInstStart)
break;
}
InviteInfo invId = inst.getInviteInfo();
Invite inv = getInvite(invId.getMsgId(), invId.getComponentId());
Iterator<Alarm> alarmsIter = inv.alarmsIterator();
for (; alarmsIter.hasNext(); ) {
Alarm alarm = alarmsIter.next();
long currTrigger = alarm.getTriggerTime(instStart, instEnd);
if (currTrigger == atOrAfter) {
// Detected alarm definition change. Reset atOrAfter to 0 to force the next loop
// to choose the earliest alarm from an earliest instance at or after old nextAlarm time.
alarmDefChanged = false;
break;
}
}
// no need to look at later instances
break;
}
if (alarmDefChanged) {
// If alarm definition changed, just pick the earliest alarm from now. Without this,
// we can't change alarm definition to an earlier trigger time, e.g. from 5 minutes before
// to 10 minutes before. (bug 28630)
atOrAfter = snoozeUntil = now;
}
}
AlarmData alarmData = getNextAlarmHelper(atOrAfter, snoozeUntil, instances, startTime, forEmailAction);
if (alarmData == null && this instanceof Task) {
// special handling for Tasks
return getNextAlarmHelperForTasks(atOrAfter, snoozeUntil, forEmailAction);
} else {
return alarmData;
}
}
use of com.zimbra.cs.mailbox.calendar.InviteInfo in project zm-mailbox by Zimbra.
the class AtomFormatter method addCalendarItem.
private void addCalendarItem(CalendarItem calItem, Element feed, UserServletContext context) throws ServiceException {
Collection<Instance> instances = calItem.expandInstances(context.getStartTime(), context.getEndTime(), false);
for (Iterator<Instance> instIt = instances.iterator(); instIt.hasNext(); ) {
CalendarItem.Instance inst = instIt.next();
InviteInfo invId = inst.getInviteInfo();
Invite inv = calItem.getInvite(invId.getMsgId(), invId.getComponentId());
Element entry = feed.addElement("entry");
entry.addElement("title").setText(inv.getName());
entry.addElement("updated").setText(DateUtil.toISO8601(new Date(inst.getStart())));
entry.addElement("summary").setText(inv.getFragment());
// TODO: only personal part in name
if (inv.hasOrganizer()) {
Element author = entry.addElement("author");
author.addElement("name").setText(inv.getOrganizer().getCn());
author.addElement("email").setText(inv.getOrganizer().getAddress());
}
}
}
Aggregations