Search in sources :

Example 1 with CreateMessage

use of com.zimbra.cs.redolog.op.CreateMessage in project zm-mailbox by Zimbra.

the class Mailbox method snapshotCounts.

void snapshotCounts() throws ServiceException {
    // for write ops, update the "new messages" count in the DB appropriately
    OperationContext octxt = currentChange().octxt;
    RedoableOp player = currentChange().getRedoPlayer();
    RedoableOp recorder = currentChange().recorder;
    if (recorder != null && (player == null || (octxt != null && !octxt.isRedo()))) {
        assert (currentChange().writeChange);
        boolean isNewMessage = recorder.getOperation() == MailboxOperation.CreateMessage;
        if (isNewMessage) {
            CreateMessage cm = (CreateMessage) recorder;
            if (cm.getFolderId() == ID_FOLDER_SPAM || cm.getFolderId() == ID_FOLDER_TRASH) {
                isNewMessage = false;
            } else if ((cm.getFlags() & NON_DELIVERY_FLAGS) != 0) {
                isNewMessage = false;
            } else if (octxt != null && octxt.getSession() != null && !octxt.isDelegatedRequest(this)) {
                isNewMessage = false;
            }
            if (isNewMessage) {
                String folderList = getAccount().getPrefMailFoldersCheckedForNewMsgIndicator();
                if (folderList != null) {
                    String[] folderIds = folderList.split(",");
                    isNewMessage = false;
                    for (int i = 0; i < folderIds.length; i++) {
                        if (cm.getFolderId() == Integer.parseInt(folderIds[i])) {
                            isNewMessage = true;
                            break;
                        }
                    }
                }
            }
        }
        if (isNewMessage) {
            currentChange().recent = mData.recentMessages + 1;
        } else if (octxt != null && mData.recentMessages != 0) {
            Session s = octxt.getSession();
            if (s instanceof SoapSession || (s instanceof SoapSession.DelegateSession && ((SoapSession.DelegateSession) s).getParentSession().isOfflineSoapSession())) {
                currentChange().recent = 0;
            }
        }
    }
    if (currentChange().isMailboxRowDirty(mData)) {
        assert (currentChange().writeChange);
        if (currentChange().recent != MailboxChange.NO_CHANGE) {
            ZimbraLog.mailbox.debug("setting recent count to %d", currentChange().recent);
        }
        DbMailbox.updateMailboxStats(this);
    }
    boolean foldersTagsDirty = false;
    if (currentChange().dirty != null && currentChange().dirty.hasNotifications()) {
        assert (currentChange().writeChange);
        if (currentChange().dirty.created != null) {
            for (BaseItemInfo item : currentChange().dirty.created.values()) {
                if (item instanceof Folder) {
                    Folder folder = (Folder) item;
                    foldersTagsDirty = true;
                    if (folder.getSize() != 0) {
                        folder.saveFolderCounts(false);
                    }
                } else if (item instanceof Tag) {
                    Tag tag = (Tag) item;
                    foldersTagsDirty = true;
                    if (tag.isUnread()) {
                        tag.saveTagCounts();
                    }
                }
            }
        }
        if (currentChange().dirty.modified != null) {
            for (Change change : currentChange().dirty.modified.values()) {
                if (change.what instanceof Folder) {
                    foldersTagsDirty = true;
                    if ((change.why & (Change.UNREAD | Change.SIZE)) != 0) {
                        ((Folder) change.what).saveFolderCounts(false);
                    }
                } else if (change.what instanceof Tag) {
                    foldersTagsDirty = true;
                    if ((change.why & Change.UNREAD | Change.SIZE) != 0) {
                        ((Tag) change.what).saveTagCounts();
                    }
                } else if ((change.what instanceof MailItem)) {
                    if (change.what instanceof Conversation) {
                        uncache((MailItem) change.what);
                    } else {
                        cache((MailItem) change.what);
                    }
                }
            }
        }
        if (currentChange().dirty.deleted != null) {
            for (Change change : currentChange().dirty.deleted.values()) {
                if (change.what instanceof Folder || change.what instanceof Tag) {
                    foldersTagsDirty = true;
                    break;
                }
            }
        }
        if (foldersTagsDirty) {
            cacheFoldersTagsToMemcached();
        }
    }
    if (DebugConfig.checkMailboxCacheConsistency && currentChange().dirty != null && currentChange().dirty.hasNotifications()) {
        if (currentChange().dirty.created != null) {
            for (BaseItemInfo item : currentChange().dirty.created.values()) {
                if (item instanceof MailItem) {
                    MailItem mi = (MailItem) item;
                    DbMailItem.consistencyCheck(mi, mi.mData, mi.encodeMetadata().toString());
                }
            }
        }
        if (currentChange().dirty.modified != null) {
            for (Change change : currentChange().dirty.modified.values()) {
                if (change.what instanceof MailItem) {
                    MailItem item = (MailItem) change.what;
                    DbMailItem.consistencyCheck(item, item.mData, item.encodeMetadata().toString());
                }
            }
        }
    }
}
Also used : BaseItemInfo(com.zimbra.common.mailbox.BaseItemInfo) Change(com.zimbra.cs.session.PendingModifications.Change) CreateFolder(com.zimbra.cs.redolog.op.CreateFolder) ZFolder(com.zimbra.client.ZFolder) RefreshMountpoint(com.zimbra.cs.redolog.op.RefreshMountpoint) TargetConstraint(com.zimbra.cs.mailbox.MailItem.TargetConstraint) CreateMountpoint(com.zimbra.cs.redolog.op.CreateMountpoint) DbMailItem(com.zimbra.cs.db.DbMailItem) ZimbraMailItem(com.zimbra.common.mailbox.ZimbraMailItem) SoapSession(com.zimbra.cs.session.SoapSession) RedoableOp(com.zimbra.cs.redolog.op.RedoableOp) CreateMessage(com.zimbra.cs.redolog.op.CreateMessage) AlterItemTag(com.zimbra.cs.redolog.op.AlterItemTag) CreateTag(com.zimbra.cs.redolog.op.CreateTag) DbTag(com.zimbra.cs.db.DbTag) DbSession(com.zimbra.cs.db.DbSession) Session(com.zimbra.cs.session.Session) SoapSession(com.zimbra.cs.session.SoapSession)

Example 2 with CreateMessage

use of com.zimbra.cs.redolog.op.CreateMessage in project zm-mailbox by Zimbra.

the class Mailbox method addMessageInternal.

private Message addMessageInternal(OperationContext octxt, ParsedMessage pm, int folderId, boolean noICal, int flags, String[] tags, int conversationId, String rcptEmail, Message.DraftInfo dinfo, CustomMetadata customData, DeliveryContext dctxt, StagedBlob staged) throws IOException, ServiceException {
    assert lock.isWriteLockedByCurrentThread();
    if (pm == null) {
        throw ServiceException.INVALID_REQUEST("null ParsedMessage when adding message to mailbox " + mId, null);
    }
    if (Math.abs(conversationId) <= HIGHEST_SYSTEM_ID) {
        conversationId = ID_AUTO_INCREMENT;
    }
    CreateMessage redoPlayer = (octxt == null ? null : (CreateMessage) octxt.getPlayer());
    boolean needRedo = needRedo(octxt, redoPlayer);
    boolean isRedo = redoPlayer != null;
    Blob blob = dctxt.getIncomingBlob();
    if (blob == null) {
        throw ServiceException.FAILURE("Incoming blob not found.", null);
    }
    // make sure we're parsing headers using the target account's charset
    pm.setDefaultCharset(getAccount().getPrefMailDefaultCharset());
    // 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);
    if (!isRedo && msgidHeader != null && !isSent && mSentMessageIDs.containsKey(msgidHeader)) {
        Integer sentMsgID = mSentMessageIDs.get(msgidHeader);
        if (conversationId == ID_AUTO_INCREMENT) {
            conversationId = getConversationIdFromReferent(pm.getMimeMessage(), sentMsgID.intValue());
            ZimbraLog.mailbox.debug("duplicate detected but not deduped (%s); will try to slot into conversation %d", msgidHeader, conversationId);
        }
    }
    // caller can't set system flags other than \Draft, \Sent and \Post
    flags &= ~Flag.FLAGS_SYSTEM | Flag.BITMASK_DRAFT | Flag.BITMASK_FROM_ME | Flag.BITMASK_POST;
    // caller can't specify non-message flags
    flags &= Flag.FLAGS_GENERIC | Flag.FLAGS_MESSAGE;
    String digest;
    int msgSize;
    try {
        digest = blob.getDigest();
        msgSize = (int) blob.getRawSize();
    } catch (IOException e) {
        throw ServiceException.FAILURE("Unable to get message properties.", e);
    }
    CreateMessage redoRecorder = new CreateMessage(mId, rcptEmail, pm.getReceivedDate(), dctxt.getShared(), digest, msgSize, folderId, noICal, flags, tags, customData);
    StoreIncomingBlob storeRedoRecorder = null;
    // strip out unread flag for internal storage (don't do this before redoRecorder initialization)
    boolean unread = (flags & Flag.BITMASK_UNREAD) > 0;
    flags &= ~Flag.BITMASK_UNREAD;
    // "having attachments" is currently tracked via flags
    if (pm.hasAttachments()) {
        flags |= Flag.BITMASK_ATTACHED;
    } else {
        flags &= ~Flag.BITMASK_ATTACHED;
    }
    // priority is calculated from headers
    flags &= ~(Flag.BITMASK_HIGH_PRIORITY | Flag.BITMASK_LOW_PRIORITY);
    flags |= pm.getPriorityBitmask();
    boolean isSpam = folderId == ID_FOLDER_SPAM;
    boolean isDraft = (flags & Flag.BITMASK_DRAFT) != 0;
    // draft replies get slotted in the same conversation as their parent, if possible
    if (isDraft && !isRedo && conversationId == ID_AUTO_INCREMENT && dinfo != null && !Strings.isNullOrEmpty(dinfo.origId)) {
        try {
            ItemId iid = new ItemId(dinfo.origId, getAccountId());
            if (iid.getId() > 0 && iid.belongsTo(this)) {
                conversationId = getMessageById(octxt, iid.getId()).getConversationId();
            }
        } catch (ServiceException e) {
        }
    }
    Message msg = null;
    boolean success = false;
    CustomMetadata.CustomMetadataList extended = MetadataCallback.preDelivery(pm);
    if (customData != null) {
        if (extended == null) {
            extended = customData.asList();
        } else {
            extended.addSection(customData);
        }
    }
    Threader threader = pm.getThreader(this);
    String subject = pm.getNormalizedSubject();
    try {
        beginTransaction("addMessage", octxt, redoRecorder);
        if (isRedo) {
            rcptEmail = redoPlayer.getRcptEmail();
        }
        Tag.NormalizedTags ntags = new Tag.NormalizedTags(this, tags);
        Folder folder = getFolderById(folderId);
        // step 0: preemptively check for quota issues (actual update is done in Message.create)
        if (!getAccount().isMailAllowReceiveButNotSendWhenOverQuota()) {
            checkSizeChange(getSize() + staged.getSize());
        }
        // step 1: get an ID assigned for the new message
        int messageId = getNextItemId(!isRedo ? ID_AUTO_INCREMENT : redoPlayer.getMessageId());
        List<Conversation> mergeConvs = null;
        if (isRedo) {
            conversationId = redoPlayer.getConvId();
            // fetch the conversations that were merged in as a result of the original delivery...
            List<Integer> mergeConvIds = redoPlayer.getMergedConvIds();
            mergeConvs = new ArrayList<Conversation>(mergeConvIds.size());
            for (int mergeId : mergeConvIds) {
                try {
                    mergeConvs.add(getConversationById(mergeId));
                } catch (NoSuchItemException nsie) {
                    ZimbraLog.mailbox.debug("could not find merge conversation %d", mergeId);
                }
            }
        }
        // step 2: figure out where the message belongs
        Conversation conv = null;
        if (threader.isEnabled()) {
            boolean isReply = pm.isReply();
            if (conversationId != ID_AUTO_INCREMENT) {
                try {
                    // fetch the requested conversation
                    // (we'll ensure that it's receiving new mail after the new message is added to it)
                    conv = getConversationById(conversationId);
                    ZimbraLog.mailbox.debug("fetched explicitly-specified conversation %d", conv.getId());
                } catch (NoSuchItemException nsie) {
                    if (!isRedo) {
                        ZimbraLog.mailbox.debug("could not find explicitly-specified conversation %d", conversationId);
                        conversationId = ID_AUTO_INCREMENT;
                    }
                }
            } else if (!isRedo && !isSpam && (isReply || (!isSent && !subject.isEmpty()))) {
                List<Conversation> matches = threader.lookupConversation();
                if (matches != null && !matches.isEmpty()) {
                    // file the message into the largest conversation, then later merge any other matching convs
                    Collections.sort(matches, new MailItem.SortSizeDescending());
                    conv = matches.remove(0);
                    mergeConvs = matches;
                }
            }
        }
        if (conv != null && conv.isTagged(Flag.FlagInfo.MUTED)) {
            // adding a message to a muted conversation marks it muted and read
            unread = false;
            flags |= Flag.BITMASK_MUTED;
        }
        // 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) {
            ZimbraLog.mailbox.debug("  placing message in existing conversation %d", convTarget.getId());
        }
        CalendarPartInfo cpi = pm.getCalendarPartInfo();
        ZVCalendar iCal = null;
        if (cpi != null && CalendarItem.isAcceptableInvite(getAccount(), cpi)) {
            iCal = cpi.cal;
        }
        msg = Message.create(messageId, folder, convTarget, pm, staged, unread, flags, ntags, dinfo, noICal, iCal, extended);
        redoRecorder.setMessageId(msg.getId());
        // step 4: create a conversation for the message, if necessary
        if (threader.isEnabled() && convTarget == null) {
            if (conv == null && conversationId == ID_AUTO_INCREMENT) {
                conv = VirtualConversation.create(this, msg);
                ZimbraLog.mailbox.debug("placed message %d in vconv %d", msg.getId(), conv.getId());
                redoRecorder.setConvFirstMsgId(-1);
            } else {
                Message[] contents = null;
                VirtualConversation vconv = null;
                if (!isRedo) {
                    vconv = (VirtualConversation) conv;
                    contents = (vconv == 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.
                        }
                        // 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(conversationId, contents);
                if (vconv != null) {
                    ZimbraLog.mailbox.debug("removed vconv %d", vconv.getId());
                    vconv.removeChild(vconv.getMessage());
                }
                // associate the first message's reference hashes with the new conversation
                if (contents.length == 2) {
                    threader.changeThreadingTargets(contents[0], conv);
                }
            }
        } else {
            // conversation feature turned off
            redoRecorder.setConvFirstMsgId(-1);
        }
        redoRecorder.setConvId(conv != null && !(conv instanceof VirtualConversation) ? conv.getId() : -1);
        // if we're threading by references, associate the new message's reference hashes with its conversation
        if (!isSpam && !isDraft) {
            threader.recordAddedMessage(conv);
        }
        if (conv != null && mergeConvs != null) {
            redoRecorder.setMergedConversations(mergeConvs);
            for (Conversation smaller : mergeConvs) {
                ZimbraLog.mailbox.info("merging conversation %d for references threading", smaller.getId());
                // try {
                conv.merge(smaller);
            // } catch (ServiceException e) {
            // if (!e.getCode().equals(MailServiceException.NO_SUCH_MSG)) {
            // throw e;
            // }
            // }
            }
        }
        // conversations may have shifted, so the threader's cached state is now questionable
        threader.reset();
        // step 5: write the redolog entries
        if (dctxt.getShared()) {
            if (dctxt.isFirst() && needRedo) {
                // Log entry in redolog for blob save.  Blob bytes are logged in the StoreIncoming entry.
                // Subsequent CreateMessage ops will reference this blob.
                storeRedoRecorder = new StoreIncomingBlob(digest, msgSize, dctxt.getMailboxIdList());
                storeRedoRecorder.start(getOperationTimestampMillis());
                storeRedoRecorder.setBlobBodyInfo(blob.getFile());
                storeRedoRecorder.log();
            }
            // Link to the file created by StoreIncomingBlob.
            redoRecorder.setMessageLinkInfo(blob.getPath());
        } else {
            // Store the blob data inside the CreateMessage op.
            redoRecorder.setMessageBodyInfo(blob.getFile());
        }
        // step 6: link to existing blob
        MailboxBlob mblob = StoreManager.getInstance().link(staged, this, messageId, getOperationChangeID());
        markOtherItemDirty(mblob);
        // when we created the Message, we used the staged locator/size/digest;
        // make sure that data actually matches the final blob in the store
        msg.updateBlobData(mblob);
        if (dctxt.getMailboxBlob() == null) {
            // Set mailbox blob for in case we want to add the message to the
            // message cache after delivery.
            dctxt.setMailboxBlob(mblob);
        }
        // step 7: queue new message for indexing
        index.add(msg);
        success = true;
        // step 8: send lawful intercept message
        try {
            Notification.getInstance().interceptIfNecessary(this, pm.getMimeMessage(), "add message", folder);
        } catch (ServiceException e) {
            ZimbraLog.mailbox.error("unable to send legal intercept message", e);
        }
    } 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.
            dctxt.setFirst(false);
        }
    }
    // step 8: remember the Message-ID header so that we can avoid receiving duplicates
    if (isSent && !isRedo && msgidHeader != null) {
        mSentMessageIDs.put(msgidHeader, msg.getId());
    }
    return msg;
}
Also used : ImapMessage(com.zimbra.cs.imap.ImapMessage) Pop3Message(com.zimbra.cs.pop3.Pop3Message) MimeMessage(javax.mail.internet.MimeMessage) CreateMessage(com.zimbra.cs.redolog.op.CreateMessage) ParsedMessage(com.zimbra.cs.mime.ParsedMessage) NormalizedTags(com.zimbra.cs.mailbox.Tag.NormalizedTags) CreateFolder(com.zimbra.cs.redolog.op.CreateFolder) ZFolder(com.zimbra.client.ZFolder) CalendarPartInfo(com.zimbra.cs.mime.ParsedMessage.CalendarPartInfo) ItemId(com.zimbra.cs.service.util.ItemId) ZVCalendar(com.zimbra.common.calendar.ZCalendar.ZVCalendar) StoreIncomingBlob(com.zimbra.cs.redolog.op.StoreIncomingBlob) CopyOnWriteArrayList(java.util.concurrent.CopyOnWriteArrayList) LinkedList(java.util.LinkedList) ArrayList(java.util.ArrayList) List(java.util.List) TypedIdList(com.zimbra.cs.mailbox.util.TypedIdList) StoreIncomingBlob(com.zimbra.cs.redolog.op.StoreIncomingBlob) StagedBlob(com.zimbra.cs.store.StagedBlob) MailboxBlob(com.zimbra.cs.store.MailboxBlob) Blob(com.zimbra.cs.store.Blob) MailboxBlob(com.zimbra.cs.store.MailboxBlob) IOException(java.io.IOException) NoSuchItemException(com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException) RefreshMountpoint(com.zimbra.cs.redolog.op.RefreshMountpoint) TargetConstraint(com.zimbra.cs.mailbox.MailItem.TargetConstraint) CreateMountpoint(com.zimbra.cs.redolog.op.CreateMountpoint) AccountServiceException(com.zimbra.cs.account.AccountServiceException) ServiceException(com.zimbra.common.service.ServiceException) NormalizedTags(com.zimbra.cs.mailbox.Tag.NormalizedTags) CreateMessage(com.zimbra.cs.redolog.op.CreateMessage) AlterItemTag(com.zimbra.cs.redolog.op.AlterItemTag) CreateTag(com.zimbra.cs.redolog.op.CreateTag) DbTag(com.zimbra.cs.db.DbTag) CustomMetadata(com.zimbra.cs.mailbox.MailItem.CustomMetadata)

Aggregations

ZFolder (com.zimbra.client.ZFolder)2 DbTag (com.zimbra.cs.db.DbTag)2 TargetConstraint (com.zimbra.cs.mailbox.MailItem.TargetConstraint)2 AlterItemTag (com.zimbra.cs.redolog.op.AlterItemTag)2 CreateFolder (com.zimbra.cs.redolog.op.CreateFolder)2 CreateMessage (com.zimbra.cs.redolog.op.CreateMessage)2 CreateMountpoint (com.zimbra.cs.redolog.op.CreateMountpoint)2 CreateTag (com.zimbra.cs.redolog.op.CreateTag)2 RefreshMountpoint (com.zimbra.cs.redolog.op.RefreshMountpoint)2 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)1 BaseItemInfo (com.zimbra.common.mailbox.BaseItemInfo)1 ZimbraMailItem (com.zimbra.common.mailbox.ZimbraMailItem)1 ServiceException (com.zimbra.common.service.ServiceException)1 AccountServiceException (com.zimbra.cs.account.AccountServiceException)1 DbMailItem (com.zimbra.cs.db.DbMailItem)1 DbSession (com.zimbra.cs.db.DbSession)1 ImapMessage (com.zimbra.cs.imap.ImapMessage)1 CustomMetadata (com.zimbra.cs.mailbox.MailItem.CustomMetadata)1 NoSuchItemException (com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException)1 NormalizedTags (com.zimbra.cs.mailbox.Tag.NormalizedTags)1