Search in sources :

Example 1 with InviteChanges

use of com.zimbra.cs.mailbox.calendar.InviteChanges in project zm-mailbox by Zimbra.

the class ToXML method encodeInvitesForMessage.

private static Element encodeInvitesForMessage(Element parent, ItemIdFormatter ifmt, OperationContext octxt, Message msg, int fields, boolean neuter) throws ServiceException {
    if (fields != NOTIFY_FIELDS && !needToOutput(fields, Change.INVITE)) {
        return parent;
    }
    Element ie = parent.addNonUniqueElement(MailConstants.E_INVITE);
    Mailbox mbox = msg.getMailbox();
    for (Iterator<Message.CalendarItemInfo> iter = msg.getCalendarItemInfoIterator(); iter.hasNext(); ) {
        Message.CalendarItemInfo info = iter.next();
        CalendarItem calItem = null;
        ICalTok method = ICalTok.REQUEST;
        Invite invCi = info.getInvite();
        if (invCi != null) {
            method = Invite.lookupMethod(invCi.getMethod());
        }
        Invite invite = invCi;
        ItemId calendarItemId = info.getCalendarItemId();
        if (info.calItemCreated()) {
            try {
                calItem = mbox.getCalendarItemById(octxt, calendarItemId);
            } catch (MailServiceException.NoSuchItemException e) {
                // Calendar item has been deleted. Bug 84877 - don't include stale references to it in SOAP response
                calendarItemId = null;
            } catch (ServiceException e) {
                // eat PERM_DENIED
                if (e.getCode() != ServiceException.PERM_DENIED) {
                    throw e;
                }
                // If we can't access it.  Don't include a reference to it.
                calendarItemId = null;
            }
            // Do staleness check for invitation messages.
            if (ICalTok.REQUEST.equals(method) || ICalTok.PUBLISH.equals(method)) {
                if (calItem != null && calItem.getFolderId() != Mailbox.ID_FOLDER_TRASH) {
                    if (invCi != null) {
                        // See if the messsage's invite is outdated.
                        Invite invCurr = calItem.getInvite(invCi.getRecurId());
                        if (invCurr != null) {
                            if (invCi.getSeqNo() >= invCurr.getSeqNo()) {
                                // Invite is new or same as what's in the appointment.  Show it.
                                invite = invCi;
                            } else {
                                // Outdated.  Don't show it.
                                invite = null;
                            }
                        } else {
                            // New invite.  Show it.
                            invite = invCi;
                        }
                    } else {
                        // legacy case
                        invite = calItem.getInvite(msg.getId(), info.getComponentNo());
                    // invite == null if the invite was outdated by a newer update
                    }
                }
            }
        } else {
            // We have an invite that wasn't auto-added.
            if (invCi != null) {
                if (!Invite.isOrganizerMethod(invCi.getMethod()) || ICalTok.DECLINECOUNTER.equals(method)) {
                    invite = invCi;
                } else {
                    try {
                        calItem = mbox.getCalendarItemByUid(octxt, invCi.getUid());
                    } catch (MailServiceException.NoSuchItemException e) {
                    // ignore
                    } catch (ServiceException e) {
                        // eat PERM_DENIED
                        if (e.getCode() != ServiceException.PERM_DENIED) {
                            throw e;
                        }
                    }
                    if (calItem != null) {
                        // See if the messsage's invite is outdated.
                        Invite invCurr = calItem.getInvite(invCi.getRecurId());
                        if (invCurr != null) {
                            if (invCi.getSeqNo() >= invCurr.getSeqNo()) {
                                // Invite is new or same as what's in the appointment.  Show it.
                                invite = invCi;
                            } else {
                                // Outdated.  Don't show it.
                                invite = null;
                            }
                        } else {
                            // New invite.  Show it.
                            invite = invCi;
                        }
                    } else {
                        // Appointment doesn't exist.  The invite in the message should be displayed and the
                        // user can manually add the appointment.
                        invite = invCi;
                    }
                }
            }
        }
        if (invite != null) {
            setCalendarItemType(ie, invite.getItemType());
            encodeTimeZoneMap(ie, invite.getTimeZoneMap());
            com.zimbra.soap.mail.type.CalendarItemInfo remoteCalendarItem = null;
            if (calItem == null) {
                remoteCalendarItem = msg.getRemoteCalendarItem(invite);
                if (remoteCalendarItem != null) {
                    calendarItemId = new ItemId(remoteCalendarItem.getId(), (String) null);
                }
            }
            encodeInviteComponent(ie, ifmt, octxt, calItem, calendarItemId, invite, fields, neuter);
            ICalTok invMethod = Invite.lookupMethod(invite.getMethod());
            if (ICalTok.REQUEST.equals(invMethod) || ICalTok.PUBLISH.equals(invMethod)) {
                InviteChanges invChanges = info.getInviteChanges();
                if (invChanges != null && !invChanges.noChange()) {
                    Element comp = ie.getOptionalElement(MailConstants.E_INVITE_COMPONENT);
                    if (comp != null) {
                        comp.addAttribute(MailConstants.A_CAL_CHANGES, invChanges.toString());
                    }
                }
                if (calItem != null) {
                    boolean showAll = invite.isPublic() || allowPrivateAccess(octxt, calItem);
                    if (showAll) {
                        RecurId rid = invite.getRecurId();
                        encodeCalendarReplies(ie, calItem, invite, rid != null ? rid.getDtZ() : null);
                    }
                } else if (null != remoteCalendarItem) {
                    CalendarReply.encodeCalendarReplyList(ie, remoteCalendarItem.getCalendarReplies());
                }
            }
        }
    }
    return ie;
}
Also used : InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) MimeMessage(javax.mail.internet.MimeMessage) Message(com.zimbra.cs.mailbox.Message) Element(com.zimbra.common.soap.Element) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) ItemId(com.zimbra.cs.service.util.ItemId) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) NoSuchItemException(com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException) Mailbox(com.zimbra.cs.mailbox.Mailbox) ServiceException(com.zimbra.common.service.ServiceException) MailServiceException(com.zimbra.cs.mailbox.MailServiceException) MailServiceException(com.zimbra.cs.mailbox.MailServiceException) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 2 with InviteChanges

use of com.zimbra.cs.mailbox.calendar.InviteChanges in project zm-mailbox by Zimbra.

the class ModifyCalendarItem method modifyCalendarItem.

private Element modifyCalendarItem(ZimbraSoapContext zsc, OperationContext octxt, Element request, Account acct, Mailbox mbox, int folderId, CalendarItem calItem, Invite inv, Invite seriesInv, Element response, boolean isInterMboxMove, MailSendQueue sendQueue) throws ServiceException {
    // <M>
    Element msgElem = request.getElement(MailConstants.E_MSG);
    ModifyCalendarItemParser parser = new ModifyCalendarItemParser(inv, seriesInv);
    CalSendData dat = handleMsgElement(zsc, octxt, msgElem, acct, mbox, parser);
    dat.mDontNotifyAttendees = isInterMboxMove;
    if (!dat.mInvite.hasRecurId())
        ZimbraLog.calendar.info("<ModifyCalendarItem> id=%d, folderId=%d, subject=\"%s\", UID=%s", calItem.getId(), folderId, dat.mInvite.isPublic() ? dat.mInvite.getName() : "(private)", dat.mInvite.getUid());
    else
        ZimbraLog.calendar.info("<ModifyCalendarItem> id=%d, folderId=%d, subject=\"%s\", UID=%s, recurId=%s", calItem.getId(), folderId, dat.mInvite.isPublic() ? dat.mInvite.getName() : "(private)", dat.mInvite.getUid(), dat.mInvite.getRecurId().getDtZ());
    boolean hasRecipients;
    try {
        Address[] rcpts = dat.mMm.getAllRecipients();
        hasRecipients = rcpts != null && rcpts.length > 0;
    } catch (MessagingException e) {
        throw ServiceException.FAILURE("Checking recipients of outgoing msg ", e);
    }
    // If we are sending this to other people, then we MUST be the organizer!
    if (!dat.mInvite.isOrganizer() && hasRecipients)
        throw MailServiceException.MUST_BE_ORGANIZER("ModifyCalendarItem");
    if (!dat.mInvite.isOrganizer()) {
        // neverSent is always false for attendee users.
        dat.mInvite.setNeverSent(false);
    } else if (!dat.mInvite.hasOtherAttendees()) {
        // neverSent is always false for appointments without attendees.
        dat.mInvite.setNeverSent(false);
    } else if (hasRecipients) {
        // neverSent is set to false when attendees are notified.
        dat.mInvite.setNeverSent(false);
    } else {
        // This is the case of organizer saving an invite with attendees, but without sending the notification.
        assert (dat.mInvite.hasOtherAttendees() && !hasRecipients);
        if (!inv.hasOtherAttendees()) {
            // Special case of going from a personal appointment (no attendees) to a draft appointment with
            // attendees.  neverSent was false for being a personal appointment, so we need to explicitly set it to true.
            // This case is essentially identical to creating a new appointment with attendees without notification.
            dat.mInvite.setNeverSent(true);
        } else {
            // Set neverSent to false, but only if it wasn't already set to true before.
            // !inv.isNeverSent() ? false : true ==> inv.isNeverSent()
            dat.mInvite.setNeverSent(inv.isNeverSent());
        }
    }
    boolean echo = request.getAttributeBool(MailConstants.A_CAL_ECHO, false);
    ItemIdFormatter ifmt = new ItemIdFormatter(zsc);
    int maxSize = (int) request.getAttributeLong(MailConstants.A_MAX_INLINED_LENGTH, 0);
    boolean wantHTML = request.getAttributeBool(MailConstants.A_WANT_HTML, false);
    boolean neuter = request.getAttributeBool(MailConstants.A_NEUTER, true);
    boolean forceSend = request.getAttributeBool(MailConstants.A_CAL_FORCESEND, true);
    if (inv.isOrganizer()) {
        // Notify removed attendees before making any changes to the appointment.
        List<ZAttendee> atsCanceled = parser.getAttendeesCanceled();
        if (!inv.isNeverSent()) {
            // No need to notify for a draft appointment.
            if (!atsCanceled.isEmpty()) {
                notifyRemovedAttendees(zsc, octxt, acct, mbox, inv.getCalendarItem(), inv, atsCanceled, sendQueue);
            }
        }
        List<ZAttendee> atsAdded = parser.getAttendeesAdded();
        // Figure out if we're notifying all attendees.  Must do this before clearing recipients from dat.mMm.
        boolean notifyAllAttendees = isNotifyingAll(dat.mMm, atsAdded);
        // If notifying all the attendees update the last sequence number otherwise retain the existing value.
        if (notifyAllAttendees) {
            dat.mInvite.setLastFullSeqNo(dat.mInvite.getSeqNo());
        } else {
            dat.mInvite.setLastFullSeqNo(inv.getLastFullSeqNo());
        }
        if (inv.isRecurrence()) {
            // Clear to/cc/bcc from the MimeMessage, so that the sendCalendarMessage call only updates the organizer's
            // own appointment without notifying any attendees.  Notifications will be sent later,
            removeAllRecipients(dat.mMm);
            // (unless they are added as new series attendees)
            if (!acct.isCalendarKeepExceptionsOnSeriesTimeChange()) {
                InviteChanges ic = new InviteChanges(seriesInv, dat.mInvite);
                if (ic.isExceptionRemovingChange()) {
                    long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
                    Invite[] invites = calItem.getInvites();
                    for (Invite except : invites) {
                        if (!except.isNeverSent() && except.hasRecurId() && !except.isCancel() && inviteIsAfterTime(except, now)) {
                            List<ZAttendee> toNotify = CalendarUtils.getRemovedAttendees(except.getAttendees(), seriesInv.getAttendees(), false, acct);
                            if (!toNotify.isEmpty()) {
                                notifyRemovedAttendees(zsc, octxt, acct, mbox, calItem, except, toNotify, sendQueue);
                            }
                        }
                    }
                }
            }
            // Save the change to the series as specified by the client.
            sendCalendarMessage(zsc, octxt, folderId, acct, mbox, dat, response, true, forceSend, sendQueue);
            // Echo the updated inv in the response.
            if (echo && dat.mAddInvData != null) {
                echoAddedInvite(response, ifmt, octxt, mbox, dat.mAddInvData, maxSize, wantHTML, neuter);
            }
            boolean ignorePastExceptions = true;
            // Reflect added/removed attendees in the exception instances.
            if (!atsAdded.isEmpty() || !atsCanceled.isEmpty()) {
                addRemoveAttendeesInExceptions(octxt, mbox, inv.getCalendarItem(), atsAdded, atsCanceled, ignorePastExceptions);
            }
            // Send notifications.
            if (hasRecipients) {
                notifyCalendarItem(zsc, octxt, acct, mbox, inv.getCalendarItem(), notifyAllAttendees, atsAdded, ignorePastExceptions, sendQueue);
            }
        } else {
            // Modifying a one-off appointment or an exception instance.  There are no
            // complications like in the series update case.  Just update the invite with the
            // data supplied by the client, and let the built-in notification take place.
            sendCalendarMessage(zsc, octxt, folderId, acct, mbox, dat, response, true, forceSend, sendQueue);
            // Echo the updated inv in the response.
            if (echo && dat.mAddInvData != null) {
                echoAddedInvite(response, ifmt, octxt, mbox, dat.mAddInvData, maxSize, wantHTML, neuter);
            }
        }
    } else {
        // not organizer
        // Apply the change.
        sendCalendarMessage(zsc, octxt, folderId, acct, mbox, dat, response, true, forceSend, sendQueue);
        // Echo the updated inv in the response.
        if (echo && dat.mAddInvData != null) {
            echoAddedInvite(response, ifmt, octxt, mbox, dat.mAddInvData, maxSize, wantHTML, neuter);
        }
    }
    return response;
}
Also used : InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) Address(javax.mail.Address) InternetAddress(javax.mail.internet.InternetAddress) MessagingException(javax.mail.MessagingException) ItemIdFormatter(com.zimbra.cs.service.util.ItemIdFormatter) Element(com.zimbra.common.soap.Element) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 3 with InviteChanges

use of com.zimbra.cs.mailbox.calendar.InviteChanges in project zm-mailbox by Zimbra.

the class Message method getInfoForAssociatedCalendarItem.

/**
 * Update {@code status.calItem} and {@code calendarItemInfos}
 */
private void getInfoForAssociatedCalendarItem(Account acct, Invite cur, String method, ParsedMessage pm, boolean applyToCalendar, ProcessInvitesStatus status) throws ServiceException {
    boolean calItemIsNew = false;
    boolean modifiedCalItem = false;
    boolean success = false;
    try {
        InviteChanges invChanges = null;
        // Look for organizer-provided change list.
        ZProperty changesProp = cur.getXProperty(ICalTok.X_ZIMBRA_CHANGES.toString());
        if (changesProp != null) {
            invChanges = new InviteChanges(changesProp.getValue());
            // Don't let the x-prop propagate further. This x-prop is used during transport only. Presence
            // of this x-prop in the appointment object can confuse clients.
            cur.removeXProp(ICalTok.X_ZIMBRA_CHANGES.toString());
        }
        if (!(status.intendedForMe || status.intendedForCalendarIManage)) {
            // Not intended for me. Just save the invite detail in metadata.
            CalendarItemInfo info = new CalendarItemInfo(CalendarItemInfo.CALITEM_ID_NONE, cur.getComponentNum(), cur, invChanges);
            calendarItemInfos.add(info);
            status.updatedMetadata = true;
            success = true;
            return;
        }
        OperationContext octxt = getMailbox().getOperationContext();
        ICalTok methodTok = Invite.lookupMethod(method);
        AccountAddressMatcher acctMatcher = status.getAcctMatcher();
        cur.sanitize(true);
        if (status.intendedForCalendarIManage) {
            Provisioning prov = Provisioning.getInstance();
            Account ownerAcct = prov.get(AccountBy.name, calendarIntendedFor);
            com.zimbra.soap.mail.type.CalendarItemInfo cii = getMailbox().getRemoteCalItemByUID(ownerAcct, cur.getUid(), false, false);
            CalendarItemInfo info;
            if (cii == null) {
                info = new CalendarItemInfo(CalendarItemInfo.CALITEM_ID_NONE, cur.getComponentNum(), cur, invChanges);
                calendarItemInfos.add(info);
            } else {
                int calItemId;
                String owner;
                try {
                    ItemId iid = new ItemId(cii.getId(), (String) null);
                    calItemId = iid.getId();
                    owner = iid.getAccountId();
                } catch (Exception e) {
                    calItemId = CalendarItemInfo.CALITEM_ID_NONE;
                    owner = null;
                }
                info = new CalendarItemInfo(calItemId, owner, cur.getComponentNum(), cur, invChanges);
                calendarItemInfos.add(info);
            }
            status.updatedMetadata = true;
            success = true;
            return;
        }
        status.calItem = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
        if (applyToCalendar && // replies are handled elsewhere (in Mailbox.addMessage())
        !ICalTok.REPLY.equals(methodTok) && !ICalTok.COUNTER.equals(methodTok) && !ICalTok.DECLINECOUNTER.equals(methodTok)) {
            if (status.calItem == null) {
                // Allow PUBLISH method as well depending on the preference.
                if (ICalTok.REQUEST.equals(methodTok) || (ICalTok.PUBLISH.equals(methodTok) && getAccount().getBooleanAttr(Provisioning.A_zimbraPrefCalendarAllowPublishMethodInvite, false))) {
                    if (status.autoAddNew) {
                        if (mMailbox.getAccount().sameAccount(cur.getOrganizerAccount())) {
                            // Bug 100456 ZCO sends out invites before adding a calendar entry.  If server adds
                            // Calendar entry and ZCO sees that before creating its entry, ZCO gets confused.
                            LOG.info("Mailbox %d Msg %d Don't create ORGANIZER calendar item for Invite %s", getMailboxId(), getId(), method);
                        } else {
                            int flags = 0;
                            // int flags = Flag.BITMASK_INDEXING_DEFERRED;
                            // mMailbox.incrementIndexDeferredCount(1);
                            int defaultFolder = cur.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
                            status.calItem = mMailbox.createCalendarItem(defaultFolder, flags, null, cur.getUid(), pm, cur, null);
                            calItemIsNew = true;
                            status.calItemFolderId = status.calItem.getFolderId();
                        }
                    }
                } else {
                    LOG.info("Mailbox %d Message %d SKIPPING Invite %s b/c no CalendarItem could be found", getMailboxId(), getId(), method);
                }
            } else {
                // bug 27887: Ignore when calendar request email somehow made a loop back to the
                // organizer address.  Necessary conditions are:
                // 
                // 1) This mailbox is currently organizer.
                // 2) ORGANIZER in the request is this mailbox.
                // 3) User/COS preference doesn't explicitly allow it. (bug 29777)
                // 
                // If ORGANIZER in the request is a different user this is an organizer change request,
                // which should be allowed to happen.
                boolean ignore = false;
                Invite defInv = status.calItem.getDefaultInviteOrNull();
                if (defInv != null && defInv.isOrganizer()) {
                    ZOrganizer org = cur.getOrganizer();
                    String orgAddress = org != null ? org.getAddress() : null;
                    if (acctMatcher.matches(orgAddress))
                        ignore = !acct.getBooleanAttr(Provisioning.A_zimbraPrefCalendarAllowCancelEmailToSelf, false);
                }
                if (ignore) {
                    ZimbraLog.calendar.info("Ignoring calendar request emailed from organizer to self, " + "possibly in a mail loop involving mailing lists and/or forwards; " + "calItemId=" + status.calItem.getId() + ", msgId=" + getId());
                } else {
                    // the calendar item in the folder it's currently in.
                    if (status.addRevision)
                        status.calItem.snapshotRevision(false);
                    // If updating (but not canceling) an appointment in trash folder,
                    // use default calendar folder and discard all existing invites.
                    boolean discardExistingInvites = false;
                    int calFolderId = status.calItem.getFolderId();
                    if (!cur.isCancel() && status.calItem.inTrash()) {
                        discardExistingInvites = true;
                        if (status.calItem.getType() == MailItem.Type.TASK) {
                            calFolderId = Mailbox.ID_FOLDER_TASKS;
                        } else {
                            calFolderId = Mailbox.ID_FOLDER_CALENDAR;
                        }
                    }
                    // against the current appointment data.
                    if (invChanges == null && !cur.isCancel()) {
                        Invite prev = status.calItem.getInvite(cur.getRecurId());
                        if (prev == null) {
                            // If incoming invite is a brand-new exception instance, we have to compare
                            // against the series data, but adjusted to the RECURRENCE-ID of the instance.
                            Invite series = status.calItem.getInvite(null);
                            if (series != null)
                                prev = series.makeInstanceInvite(cur.getRecurId().getDt());
                        }
                        if (prev != null)
                            invChanges = new InviteChanges(prev, cur);
                    }
                    modifiedCalItem = status.calItem.processNewInvite(pm, cur, calFolderId, CalendarItem.NEXT_ALARM_KEEP_CURRENT, true, /* preserveAlarms */
                    discardExistingInvites, false);
                    status.calItemFolderId = calFolderId;
                    status.calItem.getFolder().updateHighestMODSEQ();
                }
            }
        }
        int calItemId = status.calItem != null ? status.calItem.getId() : CalendarItemInfo.CALITEM_ID_NONE;
        CalendarItemInfo info = new CalendarItemInfo(calItemId, cur.getComponentNum(), cur, invChanges);
        calendarItemInfos.add(info);
        status.updatedMetadata = true;
        if (status.calItem != null && (calItemIsNew || modifiedCalItem)) {
            mMailbox.index.add(status.calItem);
        }
        success = true;
    } finally {
        if (!success && status.calItem != null) {
            // Error occurred and the calItem in memory may be out of sync with the database.
            // Uncache it here, because the error will be ignored by this method's caller.
            getMailbox().uncache(status.calItem);
        }
    }
}
Also used : Account(com.zimbra.cs.account.Account) InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) ItemId(com.zimbra.cs.service.util.ItemId) Provisioning(com.zimbra.cs.account.Provisioning) MessagingException(javax.mail.MessagingException) NoSuchItemException(com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException) ServiceException(com.zimbra.common.service.ServiceException) IOException(java.io.IOException) ICalTok(com.zimbra.common.calendar.ZCalendar.ICalTok) AccountAddressMatcher(com.zimbra.cs.util.AccountUtil.AccountAddressMatcher) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 4 with InviteChanges

use of com.zimbra.cs.mailbox.calendar.InviteChanges in project zm-mailbox by Zimbra.

the class CalendarItem method processNewInviteRequestOrCancel.

/**
 * @param pm
 * @param newInvite
 * @param folderId
 * @param nextAlarm
 * @param preserveAlarms
 * @param discardExistingInvites
 * @param batch - if true this call will not update the recurrence and may not persist to the data.
 *                The caller needs to persist the data by calling setContent().
 * @return
 * @throws ServiceException
 */
private boolean processNewInviteRequestOrCancel(ParsedMessage pm, Invite newInvite, int folderId, long nextAlarm, boolean preserveAlarms, boolean discardExistingInvites, boolean batch) throws ServiceException {
    // trace logging
    if (!newInvite.hasRecurId())
        ZimbraLog.calendar.info("Modifying CalendarItem: id=%d, folderId=%d, method=%s, subject=\"%s\", UID=%s", mId, getFolderId(), newInvite.getMethod(), newInvite.isPublic() ? newInvite.getName() : "(private)", mUid);
    else
        ZimbraLog.calendar.info("Modifying CalendarItem: id=%d, folderId=%d, method=%s, subject=\"%s\", UID=%s, recurId=%s", mId, getFolderId(), newInvite.getMethod(), newInvite.isPublic() ? newInvite.getName() : "(private)", mUid, newInvite.getRecurId().getDtZ());
    newInvite.sanitize(false);
    OperationContext octxt = getMailbox().getOperationContext();
    Account authAccount = octxt != null ? octxt.getAuthenticatedUser() : null;
    boolean asAdmin = octxt != null ? octxt.isUsingAdminPrivileges() : false;
    boolean isCancel = newInvite.isCancel();
    boolean requirePrivateCheck = requirePrivateCheck(newInvite);
    short rightsNeeded = isCancel ? (short) (ACL.RIGHT_DELETE | ACL.RIGHT_WRITE) : ACL.RIGHT_WRITE;
    if (!canAccess(rightsNeeded, authAccount, asAdmin, requirePrivateCheck))
        throw ServiceException.PERM_DENIED("you do not have sufficient permissions on this calendar item");
    // Don't allow moving a private appointment on behalf of another user,
    // unless that other user is a calendar resource.
    boolean isCalendarResource = getMailbox().getAccount() instanceof CalendarResource;
    boolean denyPrivateAccess = requirePrivateCheck ? !allowPrivateAccess(authAccount, asAdmin) : false;
    if (!newInvite.isPublic() || !isPublic()) {
        if (folderId != getFolderId()) {
            Folder folder = getMailbox().getFolderById(folderId);
            if (!allowPrivateAccess(folder, authAccount, asAdmin)) {
                denyPrivateAccess = true;
                if (!isCalendarResource)
                    throw ServiceException.PERM_DENIED("you do not have permission to update/cancel private calendar item in target folder");
            }
        }
    }
    // Do not allow organizer to be changed. (bug 74400)
    boolean organizerChanged = organizerChangeCheck(newInvite, true);
    ZOrganizer newOrganizer = newInvite.getOrganizer();
    // of the invite.
    if (isCancel) {
        boolean cancelAll;
        boolean outdated;
        if (!newInvite.hasRecurId()) {
            cancelAll = true;
            // Canceling series.  Check the sequencing requirement to make sure the invite isn't outdated.
            Invite series = getInvite((RecurId) null);
            // If series invite is not found, assume cancel is not outdated.
            outdated = series != null && !newInvite.isSameOrNewerVersion(series);
        } else {
            // Canceling an instance.  It's a total cancel only if mInvites has one invite and it matches
            // the recurrence id.  (subject to sequencing requirements)
            cancelAll = false;
            outdated = false;
            Invite curr = getInvite(newInvite.getRecurId());
            if (curr != null) {
                if (newInvite.isSameOrNewerVersion(curr)) {
                    cancelAll = true;
                    // See if there any non-cancel invites besides the one being canceled.
                    for (Invite inv : mInvites) {
                        if (!inv.equals(curr) && !inv.isCancel()) {
                            cancelAll = false;
                            break;
                        }
                    }
                } else {
                    // There is already a newer invite.  Ignore the cancel.
                    outdated = true;
                }
            }
        }
        if (outdated) {
            ZimbraLog.calendar.info("Ignoring outdated cancel request");
            return false;
        }
        if (cancelAll) {
            Folder trash = mMailbox.getFolderById(Mailbox.ID_FOLDER_TRASH);
            move(trash);
            // If we have revisions enabled we need to force metadata write to db because version field changed.
            if (getMaxRevisions() != 1)
                saveMetadata();
            return true;
        }
    }
    // Clear all replies if replacing appointment in trash folder with a new invite.  All existing invites are
    // being discarded, and so all existing replies must be discarded as well.
    Folder folder = getMailbox().getFolderById(folderId);
    if (!isCancel && discardExistingInvites && inTrash() && !folder.inTrash()) {
        mReplyList.mReplies.clear();
    }
    // Handle change to the series that involves time and/or recurrence.  In Exchange compatibility mode,
    // time/recurrence change blows away all exception instances.  In non-compat mode (old ZCS behavior),
    // look for change in the start time and shift the time part of exceptions' RECURRENCE-ID by the same delta.
    boolean needRecurrenceIdUpdate = false;
    ParsedDateTime oldDtStart = null;
    ParsedDuration dtStartMovedBy = null;
    ArrayList<Invite> toUpdate = new ArrayList<Invite>();
    if (!discardExistingInvites && !isCancel && newInvite.isRecurrence()) {
        Invite defInv = getDefaultInviteOrNull();
        if (defInv != null && defInv.isRecurrence()) {
            if (!getAccount().isCalendarKeepExceptionsOnSeriesTimeChange()) {
                // Exchange compatibility mode
                InviteChanges ic = new InviteChanges(defInv, newInvite);
                if (ic.isExceptionRemovingChange()) {
                    discardExistingInvites = true;
                }
            } else {
                // old ZCS behavior
                // Be careful.  If invites got delivered out of order, we may have defInv that's not
                // a series.  Imagine 1st invite received was an exception and 2nd was the series.
                // In that situation we simply skip the DTSTART shift calculation.
                oldDtStart = defInv.getStartTime();
                ParsedDateTime newDtStart = newInvite.getStartTime();
                // if (newDtStart != null && oldDtStart != null && !newDtStart.sameTime(oldDtStart))
                if (newDtStart != null && oldDtStart != null && !newDtStart.equals(oldDtStart)) {
                    // Find the series frequency.
                    Frequency freq = null;
                    IRecurrence recurrence = newInvite.getRecurrence();
                    if (recurrence != null) {
                        Iterator rulesIter = recurrence.addRulesIterator();
                        if (rulesIter != null) {
                            for (; rulesIter.hasNext(); ) {
                                Object ruleObj = rulesIter.next();
                                if (ruleObj instanceof SimpleRepeatingRule) {
                                    SimpleRepeatingRule series = (SimpleRepeatingRule) ruleObj;
                                    ZRecur recur = series.getRule();
                                    freq = recur.getFrequency();
                                    break;
                                }
                            }
                        }
                    }
                    // Maximum allowed delta depends on the frequency.
                    ParsedDuration deltaLimit = null;
                    if (freq != null) {
                        switch(freq) {
                            case DAILY:
                                deltaLimit = ParsedDuration.ONE_DAY;
                                break;
                            case WEEKLY:
                            case MONTHLY:
                            case YEARLY:
                                // Do the RECURRENCE-ID adjustment only when DTSTART moved by 7 days or less.
                                // If it moved by more, it gets too complicated to figure out what the old RECURRENCE-ID
                                // should be in the new series.  Just blow away all exceptions.
                                deltaLimit = ParsedDuration.ONE_WEEK;
                                break;
                            default:
                                // Secondly/minutely/hourly rules are too frequent to allow recurrence id shifting.
                                break;
                        }
                    }
                    if (deltaLimit != null) {
                        ParsedDuration delta = newDtStart.difference(oldDtStart);
                        if (delta.abs().compareTo(deltaLimit) < 0) {
                            needRecurrenceIdUpdate = true;
                            dtStartMovedBy = delta;
                        }
                    }
                }
            }
        }
    }
    // found, inherit from the series invite.
    if (!discardExistingInvites && preserveAlarms) {
        Invite localSeries = null;
        Invite alarmSourceInv = null;
        for (Invite inv : mInvites) {
            if (recurrenceIdsMatch(inv, newInvite)) {
                alarmSourceInv = inv;
                break;
            }
            if (!inv.hasRecurId())
                localSeries = inv;
        }
        if (alarmSourceInv == null)
            alarmSourceInv = localSeries;
        if (alarmSourceInv != null) {
            newInvite.clearAlarms();
            for (Iterator<Alarm> alarmIter = alarmSourceInv.alarmsIterator(); alarmIter.hasNext(); ) {
                newInvite.addAlarm(alarmIter.next());
            }
        }
    }
    // Is this a series update invite from ZCO?  If so, we have to treat all exceptions as local-only
    // and make them snap to series.
    boolean zcoSeriesUpdate = false;
    ZProperty xzDiscardExcepts = newInvite.getXProperty(ICalTok.X_ZIMBRA_DISCARD_EXCEPTIONS.toString());
    if (xzDiscardExcepts != null)
        zcoSeriesUpdate = xzDiscardExcepts.getBoolValue();
    // Is this an update to the series with UNTIL in the rule?  If so, we need to remove exceptions
    // whose RECURRENCE-ID come later than UNTIL. (bug 11870)
    long seriesUntil = Long.MAX_VALUE;
    if (!isCancel && !newInvite.hasRecurId()) {
        ParsedDateTime dtStart = newInvite.getStartTime();
        IRecurrence recur = newInvite.getRecurrence();
        if (recur != null && dtStart != null) {
            ICalTimeZone tz = dtStart.getTimeZone();
            // Find the repeating rule.
            Iterator<?> iter = recur.addRulesIterator();
            if (iter != null) {
                for (; iter.hasNext(); ) {
                    IRecurrence cur = (IRecurrence) iter.next();
                    if (cur.getType() == Recurrence.TYPE_REPEATING) {
                        ZRecur rrule = ((Recurrence.SimpleRepeatingRule) cur).getRule();
                        ParsedDateTime until = rrule.getUntil();
                        if (until != null)
                            seriesUntil = Math.min(until.getDateForRecurUntil(tz).getTime(), seriesUntil);
                    }
                }
            }
        }
    }
    // Check if exception instances are made obsolete by updated recurrence rule.  (bug 47061)
    Set<String> obsoletedRecurIdZs = new HashSet<String>();
    if (!isCancel && newInvite.isRecurrence()) {
        Invite seriesInv = null;
        // Find the range of existing exception instances.
        long rangeStart = Long.MAX_VALUE;
        long rangeEnd = Long.MIN_VALUE;
        for (Invite inv : mInvites) {
            if (inv.hasRecurId()) {
                RecurId rid = inv.getRecurId();
                ParsedDateTime ridDt = rid.getDt();
                if (ridDt != null) {
                    // Turn Outlook-style all-day RecurId to standard-style.
                    if (inv.isAllDayEvent() && ridDt.hasTime() && ridDt.hasZeroTime()) {
                        ParsedDateTime ridDtFixed = (ParsedDateTime) ridDt.clone();
                        ridDtFixed.setHasTime(false);
                        rid = new RecurId(ridDtFixed, rid.getRange());
                        ridDt = rid.getDt();
                    }
                    // Adjust start time if necessary.
                    RecurId adjustedRid;
                    long adjustedT;
                    if (dtStartMovedBy != null) {
                        ParsedDateTime dt = ridDt.add(dtStartMovedBy);
                        adjustedRid = new RecurId(dt, rid.getRange());
                        adjustedT = dt.getUtcTime();
                    } else {
                        adjustedRid = rid;
                        adjustedT = ridDt.getUtcTime();
                    }
                    rangeStart = Math.min(rangeStart, adjustedT);
                    rangeEnd = Math.max(rangeEnd, adjustedT);
                    // initially all instances considered obsolete
                    obsoletedRecurIdZs.add(adjustedRid.getDtZ());
                }
            } else {
                seriesInv = inv;
            }
        }
        // Extend the range by a day on both ends to compensate for all-day appointments.
        // 25 hours to accommodate DST onset dates
        long millisIn25Hours = 25 * 60 * 60 * 1000;
        if (rangeStart != Long.MAX_VALUE)
            rangeStart -= millisIn25Hours;
        if (rangeEnd != Long.MIN_VALUE)
            rangeEnd += millisIn25Hours;
        if (rangeStart != Long.MAX_VALUE && rangeEnd != Long.MIN_VALUE && rangeStart <= rangeEnd) {
            // so the final instance is included in the range
            ++rangeEnd;
            IRecurrence recur = newInvite.getRecurrence();
            if (recur instanceof RecurrenceRule) {
                RecurrenceRule rrule = (RecurrenceRule) recur;
                List<Instance> instances = rrule.expandInstances(getId(), rangeStart, rangeEnd);
                if (instances != null) {
                    for (Instance inst : instances) {
                        Invite refInv = seriesInv != null ? seriesInv : newInvite;
                        RecurId rid = inst.makeRecurId(refInv);
                        // Turn Outlook-style all-day RecurId to standard-style.
                        if (refInv.isAllDayEvent() && rid.getDt() != null) {
                            ParsedDateTime ridDtFixed = (ParsedDateTime) rid.getDt().clone();
                            ridDtFixed.setHasTime(false);
                            rid = new RecurId(ridDtFixed, rid.getRange());
                        }
                        // "Un-obsolete" the surviving recurrence ids.
                        obsoletedRecurIdZs.remove(rid.getDtZ());
                    }
                }
            } else if (recur != null) {
                // This shouldn't happen.
                ZimbraLog.calendar.warn("Expected RecurrenceRule object, but got " + recur.getClass().getName());
            }
        }
    }
    boolean addNewOne = true;
    boolean replaceExceptionBodyWithSeriesBody = false;
    boolean modifiedCalItem = false;
    // the invite which has been made obsolete by the new one coming in
    Invite prev = null;
    // Invites to remove from our blob store
    ArrayList<Invite> toRemove = new ArrayList<Invite>();
    // indexes to remove from mInvites
    ArrayList<Integer> idxsToRemove = new ArrayList<Integer>();
    // get current size because we may add to the list in the loop
    int numInvitesCurrent = mInvites.size();
    for (int i = 0; i < numInvitesCurrent; i++) {
        Invite cur = mInvites.get(i);
        // If request is a cancellation of entire appointment, simply add each invite to removal list.
        if (isCancel && !newInvite.hasRecurId()) {
            addNewOne = false;
            modifiedCalItem = true;
            toRemove.add(cur);
            idxsToRemove.add(0, i);
            continue;
        }
        // Use DTSTART for comparison rather than RECURRENCE-ID.
        if (!isCancel && cur.hasRecurId()) {
            ParsedDateTime instDtStart = cur.getStartTime();
            if (instDtStart != null && instDtStart.getUtcTime() > seriesUntil) {
                modifiedCalItem = true;
                toRemove.add(cur);
                idxsToRemove.add(0, i);
                continue;
            }
        }
        // Remove exceptions obsoleted by changed RRULE.  (bug 47061)
        if (cur.hasRecurId() && !obsoletedRecurIdZs.isEmpty()) {
            RecurId rid = cur.getRecurId();
            if (rid != null && rid.getDt() != null) {
                // Turn Outlook-style all-day RecurId to standard-style.
                ParsedDateTime ridDt = rid.getDt();
                if (cur.isAllDayEvent() && ridDt.hasTime() && ridDt.hasZeroTime()) {
                    ParsedDateTime ridDtFixed = (ParsedDateTime) ridDt.clone();
                    ridDtFixed.setHasTime(false);
                    rid = new RecurId(ridDtFixed, rid.getRange());
                }
                // Adjust start time if necessary.
                RecurId adjustedRid;
                if (dtStartMovedBy != null) {
                    ParsedDateTime dt = rid.getDt().add(dtStartMovedBy);
                    adjustedRid = new RecurId(dt, rid.getRange());
                } else {
                    adjustedRid = rid;
                }
                if (obsoletedRecurIdZs.contains(adjustedRid.getDtZ())) {
                    modifiedCalItem = true;
                    toRemove.add(cur);
                    idxsToRemove.add(0, i);
                    continue;
                }
            }
        }
        boolean matchingRecurId = recurrenceIdsMatch(cur, newInvite);
        if (discardExistingInvites || matchingRecurId) {
            if (discardExistingInvites || newInvite.isSameOrNewerVersion(cur)) {
                // Invite is local-only only if both old and new are local-only.
                newInvite.setLocalOnly(cur.isLocalOnly() && newInvite.isLocalOnly());
                toRemove.add(cur);
                // add to FRONT of list, so when we iterate for the removals we go from HIGHER TO LOWER
                // that way the numbers all match up as the list contracts!
                idxsToRemove.add(0, Integer.valueOf(i));
                boolean invalidateReplies = false;
                if (!discardExistingInvites) {
                    InviteChanges invChg = new InviteChanges(cur, newInvite);
                    invalidateReplies = invChg.isReplyInvalidatingChange();
                }
                if (discardExistingInvites || invalidateReplies) {
                    // clean up any old REPLYs that have been made obsolete by this new invite
                    mReplyList.removeObsoleteEntries(newInvite.getRecurId(), newInvite.getSeqNo(), newInvite.getDTStamp());
                } else {
                    // If the change is minor, don't discard earlier replies.  Organizer may have incremented the
                    // sequence unnecessarily, and we have to cope with this by bumping up the sequence in the
                    // replies accordingly.
                    mReplyList.upgradeEntriesToNewSeq(newInvite.getRecurId(), newInvite.getSeqNo(), newInvite.getDTStamp());
                }
                prev = cur;
                modifiedCalItem = true;
                if (isCancel && !newInvite.hasRecurId()) {
                    // can't CANCEL just the recurId=null entry -- we must delete the whole appointment
                    addNewOne = false;
                }
            } else {
                // perhaps delivered out of order.  Ignore it.
                return false;
            }
        } else if (!isCancel) {
            modifiedCalItem = true;
            boolean addToUpdateList = false;
            if (organizerChanged) {
                // If organizer is changing on any invite, change it on all invites.
                cur.setOrganizer(newOrganizer);
                addToUpdateList = true;
            }
            if (needRecurrenceIdUpdate) {
                // Adjust RECURRENCE-ID by the delta in series DTSTART, if recurrence id value has the
                // same time of day as old DTSTART.
                RecurId rid = cur.getRecurId();
                if (rid != null && rid.getDt() != null && oldDtStart != null) {
                    ParsedDateTime ridDt = rid.getDt();
                    if (ridDt.sameTime(oldDtStart)) {
                        ParsedDateTime dt = rid.getDt().add(dtStartMovedBy);
                        RecurId newRid = new RecurId(dt, rid.getRange());
                        cur.setRecurId(newRid);
                        // used in RECURRENCE-ID and adjust DTEND accordingly.
                        if (cur.isCancel()) {
                            cur.setDtStart(dt);
                            ParsedDateTime dtEnd = cur.getEndTime();
                            if (dtEnd != null) {
                                ParsedDateTime dtEndMoved = dtEnd.add(dtStartMovedBy);
                                cur.setDtEnd(dtEndMoved);
                            }
                        }
                        addToUpdateList = true;
                    }
                }
            }
            // organizer) are left alone.
            if (!newInvite.hasRecurId() && cur.hasRecurId() && (zcoSeriesUpdate || cur.isLocalOnly())) {
                if (cur.isCancel()) {
                    // Local-only cancellations are undone by update to the series.
                    toRemove.add(cur);
                    // add to FRONT of list, so when we iterate for the removals we go from HIGHER TO LOWER
                    // that way the numbers all match up as the list contracts!
                    idxsToRemove.add(0, Integer.valueOf(i));
                    // clean up any old REPLYs that have been made obsolete by this new invite
                    mReplyList.removeObsoleteEntries(newInvite.getRecurId(), newInvite.getSeqNo(), newInvite.getDTStamp());
                    addToUpdateList = false;
                } else {
                    replaceExceptionBodyWithSeriesBody = true;
                    // Recreate invite with data from newInvite, but preserve alarm info.
                    Invite copy = newInvite.newCopy();
                    // It's still local-only.
                    copy.setLocalOnly(true);
                    copy.setMailItemId(cur.getMailItemId());
                    copy.setComponentNum(cur.getComponentNum());
                    copy.setSeqNo(cur.getSeqNo());
                    copy.setDtStamp(cur.getDTStamp());
                    copy.setRecurId(cur.getRecurId());
                    // because we're only dealing with exceptions
                    copy.setRecurrence(null);
                    ParsedDateTime start = cur.getRecurId().getDt();
                    if (start != null) {
                        // snap back to series start time
                        copy.setDtStart(start);
                        ParsedDuration dur = cur.getDuration();
                        if (dur != null) {
                            copy.setDtEnd(null);
                            copy.setDuration(dur);
                        } else {
                            copy.setDuration(null);
                            dur = cur.getEffectiveDuration();
                            ParsedDateTime end = null;
                            if (dur != null)
                                end = start.add(dur);
                            copy.setDtEnd(end);
                        }
                    } else {
                        copy.setDtStart(null);
                        copy.setDtEnd(cur.getEndTime());
                        copy.setDuration(null);
                    }
                    copy.clearAlarms();
                    for (Iterator<Alarm> iter = cur.alarmsIterator(); iter.hasNext(); ) {
                        copy.addAlarm(iter.next());
                    }
                    // Series was updated, so change this exception's partstat to NEEDS-ACTION.
                    ZAttendee me = copy.getMatchingAttendee(getAccount());
                    if (me != null)
                        me.setPartStat(IcalXmlStrMap.PARTSTAT_NEEDS_ACTION);
                    mInvites.set(i, copy);
                    addToUpdateList = true;
                }
            }
            if (addToUpdateList)
                toUpdate.add(cur);
        }
    }
    boolean callProcessPartStat = false;
    if (addNewOne) {
        newInvite.setCalendarItem(this);
        // unless that other user is a calendar resource.
        if (denyPrivateAccess && prev != null && !prev.isPublic() && !isCalendarResource)
            throw ServiceException.PERM_DENIED("you do not have sufficient permissions on this calendar item");
        if (prev != null && !newInvite.isOrganizer() && newInvite.sentByMe()) {
            // A non-organizer attendee is modifying data on his/her
            // appointment/task.  Any information that is tracked in
            // metadata rather than in the iCal MIME part must be
            // carried over from the last invite to the new one.
            newInvite.setPartStat(prev.getPartStat());
            if (prev.hasRsvp()) {
                newInvite.setRsvp(prev.getRsvp());
            }
            newInvite.getCalendarItem().saveMetadata();
        // No need to mark invite as modified item in mailbox as
        // it has already been marked as a created item.
        } else {
            callProcessPartStat = true;
        }
        newInvite.setClassPropSetByMe(newInvite.sentByMe());
        // retain the value and therefore don't allow the organizer to override it.
        if (prev != null && !newInvite.isOrganizer() && !newInvite.sentByMe()) {
            if (!prev.isPublic() && prev.classPropSetByMe()) {
                newInvite.setClassProp(prev.getClassProp());
                newInvite.setClassPropSetByMe(true);
            }
            if (!newInvite.hasRsvp()) {
                newInvite.setRsvp(prev.getRsvp());
            }
        }
        mInvites.add(newInvite);
        // the appointment/task stores an uber-tzmap, for its uber-recurrence
        // this might give us problems if we had two invites with conflicting TZ
        // defs....should be very unlikely
        mTzMap.add(newInvite.getTimeZoneMap());
        // TIM: don't write the blob until the end of the function (so we only do one write for the update)
        // modifyBlob(toRemove, replaceExistingInvites, toUpdate, pm, newInvite, locator, isCancel, !denyPrivateAccess);
        modifiedCalItem = true;
    } else {
    // TIM: don't write the blob until the end of the function (so we only do one write for the update)
    // modifyBlob(toRemove, replaceExistingInvites, toUpdate, null, null, locator, isCancel, !denyPrivateAccess);
    }
    // now remove the inviteid's from our list
    for (Iterator<Integer> iter = idxsToRemove.iterator(); iter.hasNext(); ) {
        assert (modifiedCalItem);
        Integer i = iter.next();
        mInvites.remove(i.intValue());
    }
    // Check if there are any surviving non-cancel invites after applying the update.
    // Also check for changes in flags.
    int oldFlags = mData.getFlags();
    int newFlags = mData.getFlags() & ~(Flag.BITMASK_ATTACHED | Flag.BITMASK_DRAFT | Flag.BITMASK_HIGH_PRIORITY | Flag.BITMASK_LOW_PRIORITY);
    boolean hasSurvivingRequests = false;
    for (Invite cur : mInvites) {
        String method = cur.getMethod();
        if (method.equals(ICalTok.REQUEST.toString()) || method.equals(ICalTok.PUBLISH.toString())) {
            hasSurvivingRequests = true;
            if (cur.hasAttachment())
                newFlags |= Flag.BITMASK_ATTACHED;
            if (cur.isDraft())
                newFlags |= Flag.BITMASK_DRAFT;
            if (cur.isHighPriority())
                newFlags |= Flag.BITMASK_HIGH_PRIORITY;
            if (cur.isLowPriority())
                newFlags |= Flag.BITMASK_LOW_PRIORITY;
        }
    }
    if (newFlags != oldFlags) {
        mData.setFlags(newFlags);
        modifiedCalItem = true;
    }
    if (!hasSurvivingRequests) {
        if (!isCancel)
            ZimbraLog.calendar.warn("Invalid state: deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " while processing a non-cancel request");
        else
            ZimbraLog.calendar.warn("Invalid state: deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " because it has no invite after applying cancel invite");
        // delete this appointment/task from the table,
        delete();
        // it doesn't have anymore REQUESTs!
        return false;
    } else {
        if (nextAlarm > 0 && mAlarmData != null && mAlarmData.getNextAtBase() != nextAlarm)
            modifiedCalItem = true;
        if (modifiedCalItem) {
            if (!batch && !updateRecurrence(nextAlarm)) {
                // no default invite!  This appointment/task no longer valid
                ZimbraLog.calendar.warn("Invalid state: deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " because it has no invite");
                delete();
                return false;
            } else {
                if (callProcessPartStat) {
                    // processPartStat() must be called after
                    // updateRecurrence() has been called.  (bug 8072)
                    processPartStat(newInvite, pm != null ? pm.getMimeMessage() : null, false, newInvite.getPartStat());
                }
                if (getFolderId() != folderId) {
                    // Move appointment/task to a different folder.
                    move(folder);
                }
                // Did the appointment have a blob before the change?
                boolean hadBlobPart = false;
                Invite[] oldInvs = getInvites();
                if (oldInvs != null) {
                    for (Invite oldInv : oldInvs) {
                        if (oldInv.hasBlobPart()) {
                            hadBlobPart = true;
                            break;
                        }
                    }
                }
                // Update blob if adding a new ParsedMessage or if there is already a blob, in which
                // case we may have to delete a section from it.
                boolean newInvHasBlobPart = newInvite.hasBlobPart();
                if (hadBlobPart || newInvHasBlobPart) {
                    if (addNewOne) {
                        modifyBlob(toRemove, discardExistingInvites, toUpdate, pm, newInvite, isCancel, !denyPrivateAccess, true, replaceExceptionBodyWithSeriesBody);
                    } else {
                        if (!newInvHasBlobPart)
                            // force existing MIME part to be removed
                            toRemove.add(newInvite);
                        modifyBlob(toRemove, discardExistingInvites, toUpdate, null, null, isCancel, !denyPrivateAccess, true, replaceExceptionBodyWithSeriesBody);
                    }
                // TIM: modifyBlob will save the metadata for us as a side-effect
                // saveMetadata();
                } else {
                    markItemModified(Change.INVITE);
                    try {
                        if (batch) {
                            persistBatchedChanges = true;
                        } else {
                            // call setContent here so that MOD_CONTENT is updated...this is required
                            // for the index entry to be correctly updated (bug 39463)
                            setContent(null, null);
                        }
                    } catch (IOException e) {
                        throw ServiceException.FAILURE("IOException", e);
                    }
                }
                // remove the item if all the instances are canceled.
                Invite defInvite = getDefaultInviteOrNull();
                if (defInvite != null) {
                    Collection<Instance> instances = expandInstances(0, Long.MAX_VALUE, false);
                    if (instances.isEmpty()) {
                        ZimbraLog.calendar.warn("Deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " because it has no invite after applying request/cancel invite");
                        delete();
                        return true;
                    }
                }
                Callback cb = getCallback();
                if (cb != null)
                    cb.modified(this);
                return true;
            }
        } else {
            if (getFolderId() != folderId) {
                // Move appointment/task to a different folder.
                move(folder);
            }
            return false;
        }
    }
}
Also used : Account(com.zimbra.cs.account.Account) InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) ParsedDuration(com.zimbra.common.calendar.ParsedDuration) ArrayList(java.util.ArrayList) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Iterator(java.util.Iterator) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) SimpleRepeatingRule(com.zimbra.cs.mailbox.calendar.Recurrence.SimpleRepeatingRule) CalendarResource(com.zimbra.cs.account.CalendarResource) HashSet(java.util.HashSet) RecurrenceRule(com.zimbra.cs.mailbox.calendar.Recurrence.RecurrenceRule) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) IOException(java.io.IOException) ZRecur(com.zimbra.cs.mailbox.calendar.ZRecur) Alarm(com.zimbra.cs.mailbox.calendar.Alarm) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty) Frequency(com.zimbra.cs.mailbox.calendar.ZRecur.Frequency) Invite(com.zimbra.cs.mailbox.calendar.Invite) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Aggregations

Invite (com.zimbra.cs.mailbox.calendar.Invite)4 InviteChanges (com.zimbra.cs.mailbox.calendar.InviteChanges)4 ICalTok (com.zimbra.common.calendar.ZCalendar.ICalTok)2 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)2 ServiceException (com.zimbra.common.service.ServiceException)2 Element (com.zimbra.common.soap.Element)2 Account (com.zimbra.cs.account.Account)2 NoSuchItemException (com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException)2 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)2 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)2 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)2 ItemId (com.zimbra.cs.service.util.ItemId)2 IOException (java.io.IOException)2 MessagingException (javax.mail.MessagingException)2 ICalTimeZone (com.zimbra.common.calendar.ICalTimeZone)1 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)1 ParsedDuration (com.zimbra.common.calendar.ParsedDuration)1 CalendarResource (com.zimbra.cs.account.CalendarResource)1 Provisioning (com.zimbra.cs.account.Provisioning)1 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)1