queueForIndexing(msg, pm);
queueForIndexing(msg, false, pm);
    private synchronized Message addMessageInternal(OperationContext octxt, ParsedMessage pm, int folderId,                boolean noICal, int flags, String tagStr, int conversationId,                 String rcptEmail, Message.DraftInfo dinfo,                SharedDeliveryContext sharedDeliveryCtxt)    throws IOException, ServiceException {        if (pm == null)            throw ServiceException.INVALID_REQUEST("null ParsedMessage when adding message to mailbox " + mId, null);        boolean debug = ZimbraLog.mailbox.isDebugEnabled();        byte[] data;        String digest;        int msgSize;        try {            data = pm.getRawData();  digest = pm.getRawDigest();  msgSize = pm.getRawSize();        } catch (MessagingException me) {            throw MailServiceException.MESSAGE_PARSE_ERROR(me);        }        if (conversationId <= HIGHEST_SYSTEM_ID)            conversationId = ID_AUTO_INCREMENT;        boolean needRedo = octxt == null || octxt.needRedo();        CreateMessage redoPlayer = (octxt == null ? null : (CreateMessage) octxt.getPlayer());        boolean isRedo = redoPlayer != null;        // quick check to make sure we don't deliver 5 copies of the same message        String msgidHeader = pm.getMessageID();        boolean isSent = ((flags & Flag.BITMASK_FROM_ME) != 0);        boolean checkDuplicates = (!isRedo && msgidHeader != null);        if (checkDuplicates && !isSent && mSentMessageIDs.containsKey(msgidHeader)) {            Integer sentMsgID = (Integer) mSentMessageIDs.get(msgidHeader);            // if the rules say to drop this duplicated incoming message, return null now            if (dedupe(pm.getMimeMessage(), sentMsgID))                return null;            // if we're not dropping the new message, see if it goes in the same conversation as the old sent message            if (conversationId == ID_AUTO_INCREMENT) {                conversationId = getConversationIdFromReferent(pm.getMimeMessage(), sentMsgID.intValue());                if (debug)  ZimbraLog.mailbox.debug("  duplicate detected but not deduped (" + msgidHeader + "); " +                            "will try to slot into conversation " + conversationId);            }        }        CreateMessage redoRecorder =            new CreateMessage(mId, rcptEmail, pm.getReceivedDate(),                              sharedDeliveryCtxt.getShared(),                              digest, msgSize, folderId, noICal, flags, tagStr);        StoreIncomingBlob storeRedoRecorder = null;        // Strip out unread flag for internal storage.        // This should not be done earlier than redoRecorder initialization.        boolean unread = (flags & Flag.BITMASK_UNREAD) > 0;        flags = flags & ~Flag.BITMASK_UNREAD;        boolean isSpam = folderId == ID_FOLDER_SPAM;        boolean isDraft = ((flags & Flag.BITMASK_DRAFT) != 0);        Message msg = null;        Blob blob = null;        MailboxBlob mboxBlob = null;        boolean success = false;        try {            beginTransaction("addMessage", octxt, redoRecorder);            if (isRedo)                rcptEmail = redoPlayer.getRcptEmail();            // "having attachments" is currently tracked via flags            if (pm.hasAttachments())                flags |= Flag.BITMASK_ATTACHED;            else                flags &= ~Flag.BITMASK_ATTACHED;            Folder folder  = getFolderById(folderId);            String subject = pm.getNormalizedSubject();            long   tags    = Tag.tagsToBitmask(tagStr);            // step 1: get an ID assigned for the new message            int messageId  = getNextItemId(!isRedo ? ID_AUTO_INCREMENT : redoPlayer.getMessageId());            if (isRedo)                conversationId = redoPlayer.getConvId();            // step 2: figure out where the message belongs            Conversation conv = null;            String hash = null;            if (!DebugConfig.disableConversation) {                if (conversationId != ID_AUTO_INCREMENT) {                    try {                        conv = getConversationById(conversationId);                        if (debug)  ZimbraLog.mailbox.debug("  fetched explicitly-specified conversation " + conv.getId());                    } catch (ServiceException e) {                        if (e.getCode() != MailServiceException.NO_SUCH_CONV)                            throw e;                        if (debug)  ZimbraLog.mailbox.debug("  could not find explicitly-specified conversation " + conversationId);                    }                } else if (!isRedo && !isSpam && !isDraft && pm.isReply()) {                    conv = getConversationByHash(hash = getHash(subject));                    if (debug)  ZimbraLog.mailbox.debug("  found conversation " + (conv == null ? -1 : conv.getId()) + " for hash: " + hash);                    // the caller can specify the received date via ParsedMessge constructor or X-Zimbra-Received header                    if (conv != null && pm.getReceivedDate() > conv.getDate() + ONE_MONTH_MILLIS) {                        // if the last message in the conv was more than 1 month ago, it's probably not related...                        conv = null;                        if (debug)  ZimbraLog.mailbox.debug("  but rejected it because it's too old");                    }                }            }            // step 3: create the message and update the cache            //         and if the message is also an invite, deal with the calendar item            Conversation convTarget = (conv instanceof VirtualConversation ? null : conv);            if (convTarget != null && debug)                ZimbraLog.mailbox.debug("  placing message in existing conversation " + convTarget.getId());            short volumeId = !isRedo ? Volume.getCurrentMessageVolume().getId() : redoPlayer.getVolumeId();            ZVCalendar iCal = pm.getiCalendar();            msg = Message.create(messageId, folder, convTarget, pm, msgSize, digest,                        volumeId, unread, flags, tags, dinfo, noICal, iCal);            redoRecorder.setMessageId(msg.getId());            // step 4: create a conversation for the message, if necessary            if (!DebugConfig.disableConversation && convTarget == null) {                if (conv == null && conversationId == ID_AUTO_INCREMENT) {                    conv = VirtualConversation.create(this, msg);                    if (debug)  ZimbraLog.mailbox.debug("  placed message " + msg.getId() + " in vconv " + conv.getId());                    redoRecorder.setConvFirstMsgId(-1);                } else {                    Message[] contents = null;                    VirtualConversation vconv = null;                    if (!isRedo) {                        vconv = (VirtualConversation) conv;                        contents = (conv == null ? new Message[] { msg } : new Message[] { vconv.getMessage(), msg });                    } else {                        // Executing redo.                        int convFirstMsgId = redoPlayer.getConvFirstMsgId();                        Message convFirstMsg = null;                        // If there was a virtual conversation, then...                        if (convFirstMsgId > 0) {                            try {                                convFirstMsg = getMessageById(octxt, redoPlayer.getConvFirstMsgId());                            } catch (MailServiceException e) {                                if (!MailServiceException.NO_SUCH_MSG.equals(e.getCode()))                                    throw e;                                // The first message of conversation may have been deleted                                // by user between the time of original operation and redo.                                // Handle the case by skipping the updating of its                                // conversation ID.                            }                            // The message may have become part of a real conversation                            // between the original operation and redo.  Leave it alone                            // in that case, and only join it to this message's conversation                            // if it is still a standalone message.                            if (convFirstMsg != null && convFirstMsg.getConversationId() < 0) {                                contents = new Message[] { convFirstMsg, msg };                                vconv = new VirtualConversation(this, convFirstMsg);                            }                        }                        if (contents == null)                            contents = new Message[] { msg };                    }                    redoRecorder.setConvFirstMsgId(vconv != null ? vconv.getMessageId() : -1);                    conv = createConversation(contents, conversationId);                    if (vconv != null) {                        if (debug)  ZimbraLog.mailbox.debug("  removed vconv " + vconv.getId());                        vconv.removeChild(vconv.getMessage());                    }                }                if (!isSpam && !isDraft)                    openConversation(conv, hash);            } else {                // conversation feature turned off                redoRecorder.setConvFirstMsgId(-1);            }            redoRecorder.setConvId(conv != null && !(conv instanceof VirtualConversation) ? conv.getId() : -1);            // step 5: store the blob            // TODO: Add partition support.  Need to store as many times as there            //       are unique partitions in the set of recipient mailboxes.            blob = sharedDeliveryCtxt.getBlob();            StoreManager sm = StoreManager.getInstance();            if (blob == null) {                // This mailbox is the only recipient, or it is the first                // of multiple recipients.  Save message to incoming directory.                if (!isRedo)                    blob = sm.storeIncoming(data, digest, null, msg.getVolumeId());                else                    blob = sm.storeIncoming(data, digest, redoPlayer.getPath(), redoPlayer.getVolumeId());                String blobPath = blob.getPath();                short blobVolumeId = blob.getVolumeId();                if (sharedDeliveryCtxt.getShared()) {                    markOtherItemDirty(blob);                    // Log entry in redolog for blob save.  Blob bytes are                    // logged in StoreToIncoming entry.                    if (needRedo) {                        storeRedoRecorder = new StoreIncomingBlob(digest, msgSize, sharedDeliveryCtxt.getMailboxIdList());                        storeRedoRecorder.start(getOperationTimestampMillis());                        storeRedoRecorder.setBlobBodyInfo(data, blobPath, blobVolumeId);                        storeRedoRecorder.log();                    }                    // Create a link in mailbox directory and leave the incoming                    // copy alone, so other recipients can link to it later.                    redoRecorder.setMessageLinkInfo(blobPath, blobVolumeId, msg.getVolumeId());                    mboxBlob = sm.link(blob, this, messageId, msg.getSavedSequence(), msg.getVolumeId());                } else {                    // If the only recipient, move the incoming copy into                    // mailbox directory.  This is more efficient than                    // creating a link in mailbox directory and deleting                    // incoming copy.                    mboxBlob = sm.renameTo(blob, this, messageId, msg.getSavedSequence(), msg.getVolumeId());                    // In single-recipient case the blob bytes are logged in                    // CreateMessage entry, to avoid having to write two                    // redolog entries for a single delivery.                    redoRecorder.setMessageBodyInfo(data, blobPath, blobVolumeId);                }            } else {                String srcPath;                Blob srcBlob;                MailboxBlob srcMboxBlob = sharedDeliveryCtxt.getMailboxBlob();                if (srcMboxBlob != null && srcMboxBlob.getMailbox().getId() == mId) {                    // With filter rules, a message can be copied to one or                    // more folders and optionally kept in Inbox, meaning                    // one delivery can result in multiple deliveries.  But                    // the first copy delivered will not know there are copies                    // coming, and if there was only one recipient for the                    // message, we will end up doing the rename case above.                    // Second and later copies cannot link to the blob file                    // in incoming directory because it was renamed out.                    // Instead they have to link to the MailboxBlob file of                    // the previous delivery.  (Bug 2283)                    srcPath = srcMboxBlob.getPath();                    srcBlob = srcMboxBlob.getBlob();                } else {                    // Second or later recipient in multi-recipient message.                    // Link to blob in incoming directory.                    srcPath = blob.getPath();                    srcBlob = blob;                }                redoRecorder.setMessageLinkInfo(srcPath, srcBlob.getVolumeId(), msg.getVolumeId());                mboxBlob = sm.link(srcBlob, this, messageId, msg.getSavedSequence(), msg.getVolumeId());            }            markOtherItemDirty(mboxBlob);            queueForIndexing(msg, pm);            success = true;        } finally {            if (storeRedoRecorder != null) {                if (success)  storeRedoRecorder.commit();                else          storeRedoRecorder.abort();            }            endTransaction(success);            if (success) {                // Everything worked.  Update the blob field in ParsedMessage                // so the next recipient in the multi-recipient case will link                // to this blob as opposed to saving its own copy.                sharedDeliveryCtxt.setBlob(blob);                sharedDeliveryCtxt.setMailboxBlob(mboxBlob);            }        }        // step 6: remember the Message-ID header so that we can avoid receiving duplicates        if (isSent && checkDuplicates)            mSentMessageIDs.put(msgidHeader, new Integer(msg.getId()));        if (msg != null && ZimbraLog.mailbox.isInfoEnabled())            ZimbraLog.mailbox.info("Added message id=" + msg.getId() + " digest=" + digest +                        " mailbox=" + getId() + " rcpt=" + rcptEmail);        return msg;    }