Search in sources :

Example 96 with Invite

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

the class Appointment method getBusyTimesString.

private static String getBusyTimesString(OperationContext octxt, Mailbox mbox, List<Conflict> list, TimeZone tz, Locale lc, boolean hasMoreConflicts) throws ServiceException {
    StringBuilder sb = new StringBuilder();
    int conflictCount = 0;
    for (Conflict avail : list) {
        if (!avail.isBusy())
            continue;
        // List conflicting appointments and their organizers.
        FreeBusy fb = avail.getFreeBusy();
        List<FBInstance> instances = new ArrayList<FBInstance>();
        for (Iterator<Interval> iter = fb.iterator(); iter.hasNext(); ) {
            Interval interval = iter.next();
            // busy intervals only
            if (Conflict.isBusy(interval.getStatus())) {
                // busy appointments only
                for (FBInstance fbinst : interval.getInstances()) {
                    if (Conflict.isBusy(fbinst.getFreeBusy()))
                        instances.add(fbinst);
                }
            }
        }
        for (FBInstance instance : instances) {
            Date startDate = new Date(instance.getStartTime());
            Date endDate = new Date(instance.getEndTime());
            String start = CalendarMailSender.formatDateTime(startDate, tz, lc);
            sb.append(" * ").append(start);
            String end;
            if (DateTimeUtil.sameDay(startDate, endDate, tz)) {
                end = CalendarMailSender.formatTime(endDate, tz, lc);
                sb.append(" - ").append(end);
            } else {
                end = CalendarMailSender.formatDateTime(endDate, tz, lc);
                sb.append("\r\n   - ").append(end);
            }
            int apptId = instance.getApptId();
            long recurIdDt = instance.getRecurIdDt();
            CalendarItem appt = mbox.getCalendarItemById(octxt, apptId);
            Invite inv = appt.getInviteForRecurId(recurIdDt);
            if (inv != null && inv.hasOrganizer()) {
                ZOrganizer organizer = inv.getOrganizer();
                String orgDispName;
                if (organizer.hasCn())
                    orgDispName = organizer.getCn() + " <" + organizer.getAddress() + ">";
                else
                    orgDispName = organizer.getAddress();
                sb.append(L10nUtil.getMessage(MsgKey.calendarResourceConflictScheduledBy, lc, orgDispName));
            }
            sb.append("\r\n");
            conflictCount++;
        }
    }
    if (hasMoreConflicts)
        sb.append(" * ...\r\n");
    return sb.toString();
}
Also used : FreeBusy(com.zimbra.cs.fb.FreeBusy) ArrayList(java.util.ArrayList) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) Date(java.util.Date) FBInstance(com.zimbra.cs.fb.FreeBusy.FBInstance) Invite(com.zimbra.cs.mailbox.calendar.Invite) Interval(com.zimbra.cs.fb.FreeBusy.Interval)

Example 97 with Invite

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

the class ItemActionHelper method executeRemote.

private void executeRemote() throws ServiceException, IOException {
    Account target = Provisioning.getInstance().get(Key.AccountBy.id, mIidFolder.getAccountId());
    AuthToken at = getAuthToken();
    String pxyAuthToken = Provisioning.onLocalServer(target) ? null : at.getProxyAuthToken();
    ZAuthToken zat = null;
    if (pxyAuthToken == null) {
        zat = at.toZAuthToken();
        zat.resetProxyAuthToken();
    } else {
        zat = new ZAuthToken(pxyAuthToken);
    }
    ZMailbox.Options zoptions = new ZMailbox.Options(zat, AccountUtil.getSoapUri(target));
    zoptions.setNoSession(true);
    zoptions.setTargetAccount(target.getId());
    zoptions.setTargetAccountBy(Key.AccountBy.id);
    ZMailbox zmbx = ZMailbox.getMailbox(zoptions);
    // check for mountpoints before going any further...
    ZFolder zfolder = zmbx.getFolderById(mIidFolder.toString(mAuthenticatedAccount));
    if (zfolder instanceof ZMountpoint) {
        ItemId iidTarget = new ItemId(((ZMountpoint) zfolder).getCanonicalRemoteId(), mAuthenticatedAccount.getId());
        if (!mIidFolder.equals(iidTarget)) {
            mIidFolder = iidTarget;
            if (++mHopCount > com.zimbra.soap.ZimbraSoapContext.MAX_HOP_COUNT)
                throw MailServiceException.TOO_MANY_HOPS(mIidRequestedFolder);
            schedule();
            return;
        }
    }
    boolean deleteOriginal = mOperation != Op.COPY;
    String folderStr = mIidFolder.toString();
    mCreatedIds = new ArrayList<String>(itemIds.length);
    boolean toSpam = mIidFolder.getId() == Mailbox.ID_FOLDER_SPAM;
    boolean toMailbox = !toSpam && mIidFolder.getId() != Mailbox.ID_FOLDER_TRASH;
    for (MailItem item : mMailbox.getItemById(mOpCtxt, itemIds, type)) {
        if (item == null) {
            continue;
        }
        List<Message> msgs = null;
        if (item instanceof Conversation) {
            msgs = mMailbox.getMessagesByConversation(mOpCtxt, item.getId(), SortBy.DATE_ASC, -1);
        }
        if (deleteOriginal) {
            if (msgs != null) {
                // determine which of the conversation's component messages are actually able to be moved
                boolean permDenied = false;
                for (Iterator<Message> it = msgs.iterator(); it.hasNext(); ) {
                    Message msg = it.next();
                    if (!TargetConstraint.checkItem(mTargetConstraint, msg)) {
                        it.remove();
                    } else if (!canDelete(msg)) {
                        it.remove();
                        permDenied = true;
                    }
                }
                // stop here if no messages would be moved...
                if (msgs.isEmpty()) {
                    if (permDenied) {
                        throw ServiceException.PERM_DENIED("cannot delete any messages in " + item.getType() + " " + item.getId());
                    }
                    // all messages were excluded by the TargetConstraint, so there's no failure...
                    continue;
                }
            } else {
                if (!canDelete(item)) {
                    throw ServiceException.PERM_DENIED("cannot delete existing copy of " + item.getType() + " " + item.getId());
                }
            }
        }
        boolean fromSpam = item.inSpam();
        if ((fromSpam && toMailbox) || (!fromSpam && toSpam)) {
            try {
                Folder dest = mMailbox.getFolderById(mOpCtxt, mIidFolder.getId());
                SpamReport report = new SpamReport(toSpam, "remote " + mOperation, dest.getPath());
                Folder source = mMailbox.getFolderById(mOpCtxt, item.getFolderId());
                report.setSourceFolderPath(source.getPath());
                report.setDestAccountName(target.getName());
                SpamHandler.getInstance().handle(mOpCtxt, mMailbox, item.getId(), item.getType(), report);
            } catch (OutOfMemoryError e) {
                Zimbra.halt("out of memory", e);
            } catch (Throwable t) {
                ZimbraLog.mailop.info("could not train spam filter: " + new ItemId(item).toString(), t);
            }
        }
        // since we can't apply tags to a remote object, hardwiring "tags" to null below...
        String flags = (mOperation == Op.UPDATE && mFlags != null ? mFlags : item.getFlagString());
        String name = ((mOperation == Op.RENAME || mOperation == Op.UPDATE) && mName != null ? mName : item.getName());
        String createdId = null;
        InputStream in = null;
        switch(item.getType()) {
            case CONTACT:
                Contact ct = (Contact) item;
                Map<String, ZMailbox.ZAttachmentInfo> attachments = new HashMap<String, ZMailbox.ZAttachmentInfo>();
                for (Contact.Attachment att : ct.getAttachments()) {
                    String attachmentId = zmbx.uploadAttachment(att.getFilename(), att.getContent(), att.getContentType(), 0);
                    ZMailbox.ZAttachmentInfo info = new ZMailbox.ZAttachmentInfo().setAttachmentId(attachmentId);
                    attachments.put(att.getName(), info);
                }
                Map<String, String> fields = ct.getFields();
                Map<String, String> members = new HashMap<String, String>();
                for (String key : fields.keySet()) {
                    if (ContactConstants.A_groupMember.equals(key)) {
                        String memberEncoded = fields.get(key);
                        ContactGroup group = ContactGroup.init(memberEncoded);
                        for (Member m : group.getMembers()) {
                            members.put(m.getValue(), m.getType().getSoapEncoded());
                        }
                        break;
                    }
                }
                fields.remove(ContactConstants.A_groupMember);
                ZContact contact = zmbx.createContact(folderStr, null, fields, attachments, members);
                createdId = contact.getId();
                mCreatedIds.add(createdId);
                break;
            case MESSAGE:
                try {
                    in = StoreManager.getInstance().getContent(item.getBlob());
                    createdId = zmbx.addMessage(folderStr, flags, null, item.getDate(), in, item.getSize(), true);
                } finally {
                    ByteUtil.closeStream(in);
                }
                mCreatedIds.add(createdId);
                break;
            case VIRTUAL_CONVERSATION:
            case CONVERSATION:
                for (Message msg : msgs) {
                    flags = (mOperation == Op.UPDATE && mFlags != null ? mFlags : msg.getFlagString());
                    try {
                        in = StoreManager.getInstance().getContent(msg.getBlob());
                        createdId = zmbx.addMessage(folderStr, flags, null, msg.getDate(), in, msg.getSize(), true);
                    } finally {
                        ByteUtil.closeStream(in);
                    }
                    mCreatedIds.add(createdId);
                }
                break;
            case DOCUMENT:
                Document doc = (Document) item;
                SoapHttpTransport transport = new SoapHttpTransport(zoptions.getUri());
                try {
                    in = StoreManager.getInstance().getContent(doc.getBlob());
                    String uploadId = zmbx.uploadContentAsStream(name, in, doc.getContentType(), doc.getSize(), 4000, true);
                    // instead of using convenience method from ZMailbox
                    // we need to hand marshall the request and set the
                    // response protocol explicitly to what was requested
                    // from the client.
                    Element req = new XMLElement(MailConstants.SAVE_DOCUMENT_REQUEST);
                    Element edoc = req.addUniqueElement(MailConstants.E_DOC);
                    edoc.addAttribute(MailConstants.A_NAME, name);
                    edoc.addAttribute(MailConstants.A_FOLDER, folderStr);
                    edoc.addAttribute(MailConstants.A_FLAGS, flags);
                    Element upload = edoc.addElement(MailConstants.E_UPLOAD);
                    upload.addAttribute(MailConstants.A_ID, uploadId);
                    transport.setResponseProtocol(mResponseProtocol);
                    transport.setAuthToken(zat);
                    Element response = transport.invoke(req);
                    createdId = response.getElement(MailConstants.E_DOC).getAttribute(MailConstants.A_ID);
                } finally {
                    ByteUtil.closeStream(in);
                    transport.shutdown();
                }
                mCreatedIds.add(createdId);
                break;
            case APPOINTMENT:
            case TASK:
                CalendarItem cal = (CalendarItem) item;
                // private calendar item may not be moved by non-owner unless permission was granted
                if (!cal.isPublic()) {
                    boolean asAdmin = mOpCtxt != null ? mOpCtxt.isUsingAdminPrivileges() : false;
                    if (!cal.allowPrivateAccess(mAuthenticatedAccount, asAdmin))
                        throw ServiceException.PERM_DENIED("you do not have permission to move/copy a private calendar item from the current folder/mailbox");
                }
                // Move the item to remote mailbox using SetAppointmentRequest/SetTaskRequest.
                QName qname = (item.getType() == MailItem.Type.TASK ? MailConstants.SET_TASK_REQUEST : MailConstants.SET_APPOINTMENT_REQUEST);
                Element request = new Element.XMLElement(qname).addAttribute(MailConstants.A_FOLDER, folderStr).addAttribute(MailConstants.A_FLAGS, flags);
                ToXML.encodeAlarmTimes(request, cal);
                Invite invDefault = cal.getDefaultInviteOrNull();
                // Takeover as organizer if we're doing a MOVE and source mailbox is the organizer.
                // Don't takeover in a COPY operation.
                boolean takeoverAsOrganizer = false;
                boolean blockMove = false;
                if (Op.MOVE.equals(mOperation)) {
                    Invite inv = invDefault;
                    if (inv == null) {
                        // no default invite; let's use the first invite
                        Invite[] invs = cal.getInvites();
                        if (invs != null && invs.length > 0)
                            inv = invs[0];
                    }
                    takeoverAsOrganizer = inv != null && inv.isOrganizer();
                    blockMove = takeoverAsOrganizer && inv.hasOtherAttendees();
                }
                if (blockMove) {
                    throw MailServiceException.INVALID_REQUEST("This operation requires change of organizer and it is not permitted", null);
                }
                if (invDefault != null) {
                    addCalendarPart(request.addUniqueElement(MailConstants.A_DEFAULT), cal, invDefault, zmbx, target, takeoverAsOrganizer);
                }
                for (Invite inv : cal.getInvites()) {
                    if (inv == null || inv == invDefault)
                        continue;
                    String elem = inv.isCancel() ? MailConstants.E_CAL_CANCEL : MailConstants.E_CAL_EXCEPT;
                    addCalendarPart(request.addElement(elem), cal, inv, zmbx, target, takeoverAsOrganizer);
                }
                ToXML.encodeCalendarReplies(request, cal);
                createdId = zmbx.invoke(request).getAttribute(MailConstants.A_CAL_ID);
                mCreatedIds.add(createdId);
                break;
            default:
                throw MailServiceException.CANNOT_COPY(item.getId());
        }
        try {
            if (deleteOriginal && !mIdFormatter.formatItemId(item).equals(createdId)) {
                if (msgs == null) {
                    mMailbox.delete(mOpCtxt, item.getId(), item.getType());
                } else {
                    for (Message msg : msgs) mMailbox.delete(mOpCtxt, msg.getId(), msg.getType());
                }
            }
        } catch (ServiceException e) {
            if (e.getCode() != ServiceException.PERM_DENIED)
                throw e;
            // something funky happened permissions-wise between the getEffectivePermissions check and here...
            ZimbraLog.misc.info("could not delete original item " + item.getId() + "; treating operation as a copy instead");
        }
    }
}
Also used : ZMountpoint(com.zimbra.client.ZMountpoint) Account(com.zimbra.cs.account.Account) Message(com.zimbra.cs.mailbox.Message) MimeMessage(javax.mail.internet.MimeMessage) HashMap(java.util.HashMap) Element(com.zimbra.common.soap.Element) XMLElement(com.zimbra.common.soap.Element.XMLElement) Conversation(com.zimbra.cs.mailbox.Conversation) Folder(com.zimbra.cs.mailbox.Folder) ZFolder(com.zimbra.client.ZFolder) Document(com.zimbra.cs.mailbox.Document) XMLElement(com.zimbra.common.soap.Element.XMLElement) ZAuthToken(com.zimbra.common.auth.ZAuthToken) ItemId(com.zimbra.cs.service.util.ItemId) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) ZMailbox(com.zimbra.client.ZMailbox) ZFolder(com.zimbra.client.ZFolder) SoapHttpTransport(com.zimbra.common.soap.SoapHttpTransport) Member(com.zimbra.cs.mailbox.ContactGroup.Member) InputStream(java.io.InputStream) QName(org.dom4j.QName) ZContact(com.zimbra.client.ZContact) Contact(com.zimbra.cs.mailbox.Contact) MailItem(com.zimbra.cs.mailbox.MailItem) ServiceException(com.zimbra.common.service.ServiceException) MailServiceException(com.zimbra.cs.mailbox.MailServiceException) SpamReport(com.zimbra.cs.service.util.SpamHandler.SpamReport) AuthToken(com.zimbra.cs.account.AuthToken) ZAuthToken(com.zimbra.common.auth.ZAuthToken) ContactGroup(com.zimbra.cs.mailbox.ContactGroup) Invite(com.zimbra.cs.mailbox.calendar.Invite) ZContact(com.zimbra.client.ZContact)

Example 98 with Invite

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

the class ItemActionHelper method addCalendarPart.

private void addCalendarPart(Element parent, CalendarItem cal, Invite inv, ZMailbox zmbx, Account target, boolean takeoverAsOrganizer) throws ServiceException {
    parent.addAttribute(MailConstants.A_CAL_PARTSTAT, inv.getPartStat());
    Element m = parent.addUniqueElement(MailConstants.E_MSG);
    Pair<MimeMessage, Integer> spinfo = cal.getSubpartMessageData(inv.getMailItemId());
    if (spinfo != null) {
        try {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(spinfo.getSecond());
            spinfo.getFirst().writeTo(baos);
            String uploadId = zmbx.uploadAttachment("message", baos.toByteArray(), MimeConstants.CT_MESSAGE_RFC822, 6000);
            m.addAttribute(MailConstants.A_ATTACHMENT_ID, uploadId);
        } catch (IOException ioe) {
            ZimbraLog.misc.info("could not read subpart message for part " + inv.getComponentNum() + " of item " + cal.getId(), ioe);
        } catch (MessagingException me) {
            ZimbraLog.misc.info("could not read subpart message for part " + inv.getComponentNum() + " of item " + cal.getId(), me);
        }
    }
    if (takeoverAsOrganizer && inv.isOrganizer() && inv.hasOrganizer()) {
        Invite invCopy = inv.newCopy();
        invCopy.setInviteId(inv.getMailItemId());
        // Increment SEQUENCE and bring DTSTAMP current because we're changing organizer.
        invCopy.setSeqNo(inv.getSeqNo() + 1);
        invCopy.setDtStamp(System.currentTimeMillis());
        ZOrganizer org = invCopy.getOrganizer();
        org.setAddress(target.getName());
        org.setCn(target.getDisplayName());
        Account authAcct = mOpCtxt != null ? mOpCtxt.getAuthenticatedUser() : target;
        if (authAcct == null || authAcct.equals(target))
            org.setSentBy(null);
        else
            org.setSentBy(authAcct.getName());
        inv = invCopy;
    }
    // explicitly add the invite metadata here
    ToXML.encodeInvite(m, mIdFormatter, getOpCtxt(), cal, inv, true);
}
Also used : Account(com.zimbra.cs.account.Account) MimeMessage(javax.mail.internet.MimeMessage) MessagingException(javax.mail.MessagingException) Element(com.zimbra.common.soap.Element) XMLElement(com.zimbra.common.soap.Element.XMLElement) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) ByteArrayOutputStream(java.io.ByteArrayOutputStream) IOException(java.io.IOException) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 99 with Invite

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

the class ModifyCalendarItem method handle.

@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
    ZimbraSoapContext zsc = getZimbraSoapContext(context);
    Account acct = getRequestedAccount(zsc);
    Mailbox mbox = getRequestedMailbox(zsc);
    OperationContext octxt = getOperationContext(zsc, context);
    // proxy handling
    Element msgElem = request.getElement(MailConstants.E_MSG);
    String folderStr = msgElem.getAttribute(MailConstants.A_FOLDER, null);
    ItemId iid = new ItemId(request.getAttribute(MailConstants.A_ID), zsc);
    if (!iid.belongsTo(acct)) {
        // Proxy it.
        if (folderStr != null) {
            // make sure that the folder ID is fully qualified
            ItemId folderFQ = new ItemId(folderStr, zsc);
            msgElem.addAttribute(MailConstants.A_FOLDER, folderFQ.toString());
        }
        return proxyRequest(request, context, iid.getAccountId());
    }
    // Check if moving to a different mailbox.
    boolean isInterMboxMove = false;
    ItemId iidFolder = null;
    if (folderStr != null) {
        iidFolder = new ItemId(folderStr, zsc);
        isInterMboxMove = !iidFolder.belongsTo(mbox);
    }
    MailSendQueue sendQueue = new MailSendQueue();
    Element response = getResponseElement(zsc);
    int compNum = (int) request.getAttributeLong(MailConstants.A_CAL_COMP, 0);
    mbox.lock.lock();
    try {
        CalendarItem calItem = mbox.getCalendarItemById(octxt, iid.getId());
        if (calItem == null) {
            throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
        }
        // Reject the request if calendar item is under trash or is being moved to trash.
        if (calItem.inTrash()) {
            throw ServiceException.INVALID_REQUEST("cannot modify a calendar item under trash", null);
        }
        if (!isInterMboxMove && iidFolder != null) {
            if (iidFolder.getId() != calItem.getFolderId()) {
                Folder destFolder = mbox.getFolderById(octxt, iidFolder.getId());
                if (destFolder.inTrash()) {
                    throw ServiceException.INVALID_REQUEST("cannot combine with a move to trash", null);
                }
            }
        }
        // Conflict detection.  Do it only if requested by client.  (for backward compat)
        int modSeq = (int) request.getAttributeLong(MailConstants.A_MODIFIED_SEQUENCE, 0);
        int revision = (int) request.getAttributeLong(MailConstants.A_REVISION, 0);
        if (modSeq != 0 && revision != 0 && (modSeq < calItem.getModifiedSequence() || revision < calItem.getSavedSequence())) {
            throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
        }
        Invite inv = calItem.getInvite(iid.getSubpartId(), compNum);
        if (inv == null) {
            throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
        }
        Invite seriesInv = calItem.getDefaultInviteOrNull();
        int folderId = calItem.getFolderId();
        if (!isInterMboxMove && iidFolder != null) {
            folderId = iidFolder.getId();
        }
        modifyCalendarItem(zsc, octxt, request, acct, mbox, folderId, calItem, inv, seriesInv, response, isInterMboxMove, sendQueue);
    } finally {
        mbox.lock.release();
        sendQueue.send();
    }
    // Inter-mailbox move if necessary.
    if (isInterMboxMove) {
        CalendarItem calItem = mbox.getCalendarItemById(octxt, iid.getId());
        List<Integer> ids = new ArrayList<Integer>(1);
        ids.add(calItem.getId());
        ItemActionHelper.MOVE(octxt, mbox, zsc.getResponseProtocol(), ids, calItem.getType(), null, iidFolder);
    }
    return response;
}
Also used : OperationContext(com.zimbra.cs.mailbox.OperationContext) Account(com.zimbra.cs.account.Account) Element(com.zimbra.common.soap.Element) ArrayList(java.util.ArrayList) Folder(com.zimbra.cs.mailbox.Folder) ItemId(com.zimbra.cs.service.util.ItemId) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) Mailbox(com.zimbra.cs.mailbox.Mailbox) ZimbraSoapContext(com.zimbra.soap.ZimbraSoapContext) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 100 with Invite

use of com.zimbra.cs.mailbox.calendar.Invite 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());
            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);
            }
        }
        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)103 Account (com.zimbra.cs.account.Account)30 Element (com.zimbra.common.soap.Element)26 ServiceException (com.zimbra.common.service.ServiceException)23 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)23 Mailbox (com.zimbra.cs.mailbox.Mailbox)23 MimeMessage (javax.mail.internet.MimeMessage)23 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)22 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)20 ItemId (com.zimbra.cs.service.util.ItemId)20 ArrayList (java.util.ArrayList)19 IOException (java.io.IOException)18 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)16 TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)15 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)15 OperationContext (com.zimbra.cs.mailbox.OperationContext)14 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)14 MessagingException (javax.mail.MessagingException)12 MailServiceException (com.zimbra.cs.mailbox.MailServiceException)11 ParsedMessage (com.zimbra.cs.mime.ParsedMessage)11