use of com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException in project zm-mailbox by Zimbra.
the class OctopusPatchFormatter method saveCallback.
// Formatter API
@Override
public void saveCallback(UserServletContext context, String contentType, Folder folder, String filename) throws IOException, ServiceException, UserServletException {
log.info("Uploading patch for " + filename);
if (filename == null) {
throw new UserServletException(HttpServletResponse.SC_BAD_REQUEST, "Missing filename");
}
MailItem item = null;
Mailbox mbox = folder.getMailbox();
try {
item = mbox.getItemByPath(context.opContext, filename, folder.getId());
if (!(item instanceof Document))
throw new UserServletException(HttpServletResponse.SC_BAD_REQUEST, "cannot overwrite existing object at that path");
} catch (NoSuchItemException e) {
log.debug("No document found at " + filename + "(folder id=" + folder.getId() + "; will create new one");
}
PatchInputStream pis = null;
PatchStore.IncomingPatch ip = null;
try {
ip = patchStore.createIncomingPatch(context.targetAccount.getId());
int defaultFileId = 0;
int defaultVersion = 0;
if (item != null) {
defaultFileId = item.getId();
defaultVersion = item.getVersion();
}
pis = PatchInputStream.create(context.getRequestInputStream(), // the current (target) user's view
context.targetMailbox, context.opContext, defaultFileId, defaultVersion, ip.getOutputStream(), ip.getManifest());
String creator = context.getAuthAccount() == null ? null : context.getAuthAccount().getName();
if (contentType == null) {
contentType = MimeDetect.getMimeDetect().detect(filename);
if (contentType == null)
contentType = MimeConstants.CT_APPLICATION_OCTET_STREAM;
}
log.debug("Storing blob");
Blob blob = StoreManager.getInstance().storeIncoming(pis);
log.debug("Creating parsed document; filename=" + filename + ", contentType=" + contentType + ", creator=" + creator);
ParsedDocument pd = new ParsedDocument(blob, filename, contentType, System.currentTimeMillis(), creator, context.req.getHeader("X-Zimbra-Description"), true);
log.debug("Parsed document created " + filename);
// scan upload for viruses
StringBuffer info = new StringBuffer();
UploadScanner.Result result = UploadScanner.acceptStream(pis, info);
if (result == UploadScanner.REJECT)
throw MailServiceException.UPLOAD_REJECTED(filename, info.toString());
if (result == UploadScanner.ERROR)
throw MailServiceException.SCAN_ERROR(filename);
if (item == null) {
log.debug("Creating new document " + filename);
item = mbox.createDocument(context.opContext, folder.getId(), pd, MailItem.Type.DOCUMENT, 0);
} else {
log.debug("Creating new version of the document " + filename + ", current version: " + item.getVersion());
item = mbox.addDocumentRevision(context.opContext, item.getId(), pd);
}
patchStore.acceptPatch(ip, item.getId(), item.getVersion());
NativeFormatter.sendZimbraHeaders(context, context.resp, item);
} catch (PatchException e) {
log.error("Patch upload failed: " + e);
patchStore.rejectPatch(ip);
throw new UserServletException(HttpServletResponse.SC_CONFLICT, "patch cannot be applied, try uploading whole file", e);
} finally {
try {
pis.close();
} catch (Exception e) {
log.error("Exception during PatchInputStream close, ignored: " + e);
}
}
}
use of com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException in project zm-mailbox by Zimbra.
the class Formatter method updateClient.
protected void updateClient(UserServletContext context, Exception e, List<ServiceException> w) throws UserServletException, IOException, ServletException, ServiceException {
String callback = context.params.get(QP_CALLBACK);
Throwable exception = null;
PrintWriter out = null;
if (e != null) {
Throwable cause = e.getCause();
exception = cause instanceof UserServletException || cause instanceof ServletException || cause instanceof IOException ? cause : e;
context.logError(exception);
}
// This ensures seamless display of manual download link provided by error callback, or otherwise a null blank preview pane.
if (exception instanceof ConversionUnsupportedException && (callback == null || !callback.startsWith("ZmPreviewView._errorCallback"))) {
exception = null;
}
/* Make doubly sure that parameters are valid. In the past, a path which failed to call this during
* initial formatter setup was missed. Ideally, should have caught this issue earlier to avoid
* wasting effort.
*/
validateParams(context);
if (callback == null || callback.equals("")) {
if (context.params.get(PROGRESS) == null) {
if (exception == null) {
return;
} else if (exception instanceof UserServletException) {
throw (UserServletException) exception;
} else if (exception instanceof ServletException) {
throw (ServletException) exception;
} else if (exception instanceof IOException) {
throw (IOException) exception;
} else if (exception instanceof NoSuchItemException) {
throw (ServiceException) exception;
} else if (exception instanceof ServiceException) {
ServiceException se = (ServiceException) exception;
if (se.getCode() == ServiceException.INVALID_REQUEST) {
throw se;
}
}
throw ServiceException.FAILURE(getType() + " formatter failure", exception);
}
try {
out = updateClient(context, false);
} catch (IllegalStateException ise) {
ZimbraLog.misc.warn("format output has already been written.");
return;
}
if (exception == null && (w == null || w.isEmpty())) {
out.println("<body></body>\n</html>");
} else {
ZimbraLog.misc.warn(getType() + " formatter exception", exception);
out.println("<body>\n<pre>");
if (exception != null)
out.print(exception.getLocalizedMessage());
for (ServiceException warning : w) {
out.println("<br>");
out.println(warning.toString().replace("\n", "<br>"));
}
out.println("</pre>\n</body>\n</html>");
}
} else if (!"2".equals(context.params.get(PROGRESS))) {
String result;
if (exception != null) {
if (exception instanceof ConversionUnsupportedException) {
ZimbraLog.misc.warn(getType() + " formatter exception, " + exception.getMessage());
} else {
ZimbraLog.misc.warn(getType() + " formatter exception", exception);
}
result = "fail";
} else if (w == null || w.size() == 0) {
if (context.req.getMethod().equals("GET")) {
return;
}
result = "success";
} else {
result = "warn";
}
try {
out = updateClient(context, false);
} catch (IllegalStateException ise) {
ZimbraLog.misc.warn("format output has already been written.");
return;
}
// mark done no matter what happens next
context.params.put(PROGRESS, "2");
out.println("<body onload='onLoad()'>");
out.println("<script>");
out.println("function onLoad() {");
out.print(" window.parent." + callback + "('" + result + "'");
if (exception != null) {
ServiceException se = exception instanceof ServiceException ? (ServiceException) exception : FormatterServiceException.UNKNOWN_ERROR(exception);
out.print(",\n\t");
out.print(SoapProtocol.SoapJS.soapFault(se));
}
if (w != null) {
for (ServiceException warning : w) {
out.print(",\n\t");
out.print(SoapProtocol.SoapJS.soapFault(warning));
}
}
out.println(");");
out.println("}");
out.println("</script>");
out.println("</body>");
out.println("</html>");
}
}
use of com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException in project zm-mailbox by Zimbra.
the class Message method processInvitesAfterCreate.
/**
* This has to be done as a separate step, after the MailItem has been added, because of foreign key constraints on
* the CalendarItems table.
*/
private void processInvitesAfterCreate(String method, int folderId, boolean applyToCalendar, ParsedMessage pm, List<Invite> invites) throws ServiceException {
if (pm == null) {
throw ServiceException.INVALID_REQUEST("null ParsedMessage while processing invite in message " + mId, null);
}
Account acct = getAccount();
AccountAddressMatcher acctMatcher = new AccountAddressMatcher(acct);
OperationContext octxt = getMailbox().getOperationContext();
ProcessInvitesStatus status = new ProcessInvitesStatus(acct, pm);
status.initAutoAddNew(octxt);
boolean isOrganizerMethod = Invite.isOrganizerMethod(method);
if (isOrganizerMethod && !invites.isEmpty() && status.intendedForMe) {
// Check if the sender is allowed to invite this user. Only do this for invite-type methods,
// namely REQUEST/PUBLISH/CANCEL/ADD/DECLINECOUNTER. REPLY/REFRESH/COUNTER don't undergo
// the check because they are not organizer-to-attendee methods.
String senderEmail;
Account senderAcct = null;
boolean onBehalfOf = false;
boolean canInvite;
AccessManager accessMgr = AccessManager.getInstance();
if (octxt != null && octxt.getAuthenticatedUser() != null) {
onBehalfOf = octxt.isDelegatedRequest(getMailbox());
senderAcct = octxt.getAuthenticatedUser();
senderEmail = senderAcct.getName();
canInvite = accessMgr.canDo(senderAcct, acct, User.R_invite, octxt.isUsingAdminPrivileges());
} else {
senderEmail = pm.getSenderEmail(false);
if (senderEmail != null) {
senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
}
canInvite = accessMgr.canDo(senderEmail, acct, User.R_invite, false);
}
if (!canInvite) {
Invite invite = invites.get(0);
CalendarMailSender.handleInviteAutoDeclinedNotification(octxt, getMailbox(), acct, senderEmail, senderAcct, onBehalfOf, applyToCalendar, getId(), invite);
String inviteSender = senderEmail != null ? senderEmail : "unknown sender";
ZimbraLog.calendar.info("Calendar invite from %s to %s is not allowed", inviteSender, acct.getName());
// Turn off auto-add. We still have to run through the code below to save the invite's
// data in message's metadata.
status.autoAddNew = false;
}
}
// Override CLASS property if preference says to mark everything as private.
PrefCalendarApptVisibility prefClass = acct.getPrefCalendarApptVisibility();
boolean forcePrivateClass = prefClass != null && !prefClass.equals(PrefCalendarApptVisibility.public_);
// Ignore alarms set by organizer.
boolean allowOrganizerAlarm = DebugConfig.calendarAllowOrganizerSpecifiedAlarms;
if (calendarItemInfos == null) {
calendarItemInfos = new ArrayList<CalendarItemInfo>();
}
// properties.
if (invites.size() > 1) {
boolean hasSeries = false;
ZOrganizer seriesOrganizer = null;
boolean seriesIsOrganizer = false;
List<ZAttendee> seriesAttendees = null;
ParsedDateTime seriesDtStart = null;
// Get organizer and attendees from series VEVENT.
for (Invite inv : invites) {
if (!inv.hasRecurId()) {
hasSeries = true;
seriesOrganizer = inv.getOrganizer();
seriesIsOrganizer = inv.isOrganizer();
seriesAttendees = inv.getAttendees();
seriesDtStart = inv.getStartTime();
break;
}
}
if (hasSeries) {
for (Invite inv : invites) {
RecurId rid = inv.getRecurId();
if (rid != null) {
if (seriesOrganizer != null && !inv.hasOrganizer()) {
inv.setOrganizer(seriesOrganizer);
inv.setIsOrganizer(seriesIsOrganizer);
// exception instance to have no attendee.
if (!inv.hasOtherAttendees() && seriesAttendees != null) {
for (ZAttendee at : seriesAttendees) {
inv.addAttendee(at);
}
}
}
if (!inv.isAllDayEvent() && seriesDtStart != null) {
// Exchange can send invalid RECURRENCE-ID with HHMMSS set to 000000. Detect it and fix it up
// by copying the time from series DTSTART.
ParsedDateTime ridDt = rid.getDt();
if (ridDt != null && ridDt.hasZeroTime() && !seriesDtStart.hasZeroTime() && ridDt.sameTimeZone(seriesDtStart)) {
ParsedDateTime fixedDt = seriesDtStart.cloneWithNewDate(ridDt);
RecurId fixedRid = new RecurId(fixedDt, rid.getRange());
ZimbraLog.calendar.debug("Fixed up invalid RECURRENCE-ID with zero time; before=[%s], after=[%s]", rid, fixedRid);
inv.setRecurId(fixedRid);
}
}
// Exception instance invites shouldn't point to the same MIME part in the appointment blob
// as the series invite. If they do, we will lose the series attachment when a new exception
// instance update is received.
inv.setMailItemId(0);
}
}
}
}
// used to check if any invite is non-public
boolean publicInvites = true;
status.calItemFolderId = invites.size() > 0 && invites.get(0).isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
CalendarItem firstCalItem = null;
Set<String> calUidsSeen = new HashSet<String>();
for (Invite cur : invites) {
if (!cur.isPublic()) {
publicInvites = false;
}
// it's the correct organizer.
if (!cur.hasOrganizer() && cur.hasOtherAttendees()) {
String fromEmail = pm.getSenderEmail(true);
if (fromEmail != null) {
boolean dangerousSender = false;
// Is sender == recipient? If so, clear attendees.
if (status.intendedForAddress != null) {
if (status.intendedForAddress.equalsIgnoreCase(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
cur.clearAttendees();
dangerousSender = true;
}
} else if (acctMatcher.matches(fromEmail)) {
ZimbraLog.calendar.info("Got malformed invite without organizer. Clearing attendees to prevent inadvertent cancels.");
cur.clearAttendees();
dangerousSender = true;
}
if (!dangerousSender) {
if (isOrganizerMethod = Invite.isOrganizerMethod(method)) {
// For organizer-originated methods, use email sender as default organizer.
ZOrganizer org = new ZOrganizer(fromEmail, null);
String senderEmail = pm.getSenderEmail(false);
if (senderEmail != null && !senderEmail.equalsIgnoreCase(fromEmail))
org.setSentBy(senderEmail);
cur.setOrganizer(org);
ZimbraLog.calendar.info("Got malformed invite that lists attendees without specifying an organizer. " + "Defaulting organizer to: " + org.toString());
} else {
// For attendee-originated methods, look up organizer from appointment on calendar.
// If appointment is not found, fall back to the intended-for address, then finally to self.
ZOrganizer org = null;
CalendarItem ci = mMailbox.getCalendarItemByUid(octxt, cur.getUid());
if (ci != null) {
Invite inv = ci.getInvite(cur.getRecurId());
if (inv == null) {
inv = ci.getDefaultInviteOrNull();
}
if (inv != null) {
org = inv.getOrganizer();
}
}
if (org == null) {
if (status.intendedForAddress != null) {
org = new ZOrganizer(status.intendedForAddress, null);
} else {
org = new ZOrganizer(acct.getName(), null);
}
}
cur.setOrganizer(org);
cur.setIsOrganizer(status.intendedForMe);
ZimbraLog.calendar.info("Got malformed reply missing organizer. Defaulting to " + org.toString());
}
}
}
}
cur.setLocalOnly(false);
status.initAddRevisionSetting(cur.getUid(), calUidsSeen);
// other than BUSY. And don't allow transparent meetings. This will prevent double booking in the future.
if (cur.isEvent() && (acct instanceof CalendarResource)) {
cur.setFreeBusy(IcalXmlStrMap.FBTYPE_BUSY);
cur.setTransparency(IcalXmlStrMap.TRANSP_OPAQUE);
}
if (forcePrivateClass) {
cur.setClassProp(IcalXmlStrMap.CLASS_PRIVATE);
cur.setClassPropSetByMe(true);
}
ICalTok methodTok = Invite.lookupMethod(method);
// Discard alarms set by organizer. Add a new one based on attendee's preferences.
if (!allowOrganizerAlarm) {
// only for non-cancel/non-declinecounter VEVENTs
if (cur.isEvent() && isOrganizerMethod && !cur.isCancel() && !ICalTok.DECLINECOUNTER.equals(methodTok))
Invite.setDefaultAlarm(cur, acct);
}
getInfoForAssociatedCalendarItem(acct, cur, method, pm, applyToCalendar, status);
if (firstCalItem == null) {
firstCalItem = status.calItem;
}
}
if (status.updatedMetadata) {
saveMetadata();
}
// Don't forward from a system account. (e.g. archiving, galsync, ham/spam)
if (applyToCalendar && !status.isForwardedInvite && status.intendedForMe && folderId != Mailbox.ID_FOLDER_SENT && !invites.isEmpty() && !acct.isIsSystemResource()) {
// Don't do the forwarding during redo playback.
RedoableOp redoPlayer = octxt != null ? octxt.getPlayer() : null;
RedoLogProvider redoProvider = RedoLogProvider.getInstance();
boolean needToForward = redoProvider.isMaster() && (redoPlayer == null || redoProvider.getRedoLogManager().getInCrashRecovery());
if (needToForward) {
String[] forwardTo = null;
if (isOrganizerMethod) {
forwardTo = acct.getPrefCalendarForwardInvitesTo();
} else {
// not the users listed in the zimbraPrefCalendarForwardInvitesTo preference.
if (firstCalItem != null) {
Invite invCalItem = firstCalItem.getInvite(invites.get(0).getRecurId());
if (invCalItem == null)
invCalItem = firstCalItem.getDefaultInviteOrNull();
if (invCalItem != null && invCalItem.isOrganizer()) {
ZOrganizer org = invCalItem.getOrganizer();
if (org.hasSentBy()) {
forwardTo = new String[] { org.getSentBy() };
}
}
}
}
Account senderAcct = null;
String senderEmail = pm.getSenderEmail(false);
if (senderEmail != null)
senderAcct = Provisioning.getInstance().get(AccountBy.name, senderEmail);
if (forwardTo != null && forwardTo.length > 0) {
// recipients to receive unfiltered message
List<String> rcptsUnfiltered = new ArrayList<String>();
// recipients to receive message filtered to remove private data
List<String> rcptsFiltered = new ArrayList<String>();
Folder calFolder = null;
try {
calFolder = getMailbox().getFolderById(status.calItemFolderId);
} catch (NoSuchItemException e) {
ZimbraLog.mailbox.warn("No such calendar folder (" + status.calItemFolderId + ") during invite auto-forwarding");
}
for (String fwd : forwardTo) {
if (fwd != null) {
fwd = fwd.trim();
}
if (StringUtil.isNullOrEmpty(fwd)) {
continue;
}
// Prevent forwarding to self.
if (acctMatcher.matches(fwd))
continue;
// Don't forward back to the sender. It's redundant and confusing.
Account rcptAcct = Provisioning.getInstance().get(AccountBy.name, fwd);
boolean rcptIsSender = false;
if (rcptAcct != null) {
if (senderAcct != null) {
rcptIsSender = rcptAcct.getId().equalsIgnoreCase(senderAcct.getId());
} else {
rcptIsSender = AccountUtil.addressMatchesAccount(rcptAcct, senderEmail);
}
} else {
if (senderAcct != null) {
rcptIsSender = AccountUtil.addressMatchesAccount(senderAcct, fwd);
} else {
rcptIsSender = fwd.equalsIgnoreCase(senderEmail);
}
}
if (rcptIsSender) {
ZimbraLog.calendar.info("Not auto-forwarding to " + fwd + " because it is the sender of this message");
continue;
}
if (publicInvites) {
rcptsUnfiltered.add(fwd);
} else {
boolean allowed = false;
if (calFolder != null && rcptAcct != null) {
allowed = calFolder.canAccess(ACL.RIGHT_PRIVATE, rcptAcct, false);
}
if (allowed) {
rcptsUnfiltered.add(fwd);
} else if (acct instanceof CalendarResource) {
// Forward filtered invite from calendar resource accounts only. Don't forward filtered
// invite from regular user account because the forwardee won't be able to accept/decline
// due to permission error.
rcptsFiltered.add(fwd);
}
}
}
if (!rcptsUnfiltered.isEmpty() || !rcptsFiltered.isEmpty()) {
MimeMessage mmOrig = pm.getMimeMessage();
if (mmOrig != null) {
String origSender = pm.getSenderEmail(false);
String forwarder = AccountUtil.getCanonicalAddress(acct);
if (!rcptsUnfiltered.isEmpty()) {
MimeMessage mm = CalendarMailSender.createForwardedInviteMessage(mmOrig, origSender, forwarder, rcptsUnfiltered.toArray(new String[0]));
if (mm != null) {
ItemId origMsgId = new ItemId(getMailbox(), getId());
CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
}
}
if (!rcptsFiltered.isEmpty()) {
MimeMessage mm = CalendarMailSender.createForwardedPrivateInviteMessage(acct, acct.getLocale(), method, invites, origSender, forwarder, rcptsFiltered.toArray(new String[0]));
if (mm != null) {
ItemId origMsgId = new ItemId(getMailbox(), getId());
CalendarMailSender.sendInviteAutoForwardMessage(octxt, getMailbox(), origMsgId, mm);
}
}
}
}
}
}
}
}
use of com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException 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;
}
use of com.zimbra.cs.mailbox.MailServiceException.NoSuchItemException in project zm-mailbox by Zimbra.
the class Mailbox method getItemById.
MailItem getItemById(int id, MailItem.Type type, boolean fromDumpster) throws ServiceException {
if (fromDumpster) {
MailItem item = null;
try {
item = MailItem.getById(this, id, type, true);
if (item != null && !isVisibleInDumpster(item)) {
item = null;
}
} catch (NoSuchItemException e) {
}
// fault.
if (item == null) {
throw MailItem.noSuchItem(id, type);
}
return item;
}
// try the cache first
MailItem item = getCachedItem(Integer.valueOf(id), type);
if (item != null) {
return item;
}
// the tag and folder caches contain ALL tags and folders, so cache miss == doesn't exist
if (isCachedType(type)) {
throw MailItem.noSuchItem(id, type);
}
boolean virtualConv = false;
boolean cachedMsg = true;
boolean sameId = true;
if (id <= -FIRST_USER_ID) {
virtualConv = true;
ZimbraLog.mailbox.debug("getting virtual conversation");
// special-case virtual conversations
if (type != MailItem.Type.CONVERSATION && type != MailItem.Type.UNKNOWN) {
throw MailItem.noSuchItem(id, type);
}
Message msg = getCachedMessage(Integer.valueOf(-id));
if (msg == null) {
ZimbraLog.mailbox.debug("message not cached");
cachedMsg = false;
msg = getMessageById(-id);
}
int convId = msg.getConversationId();
if (msg.getConversationId() != id) {
ZimbraLog.mailbox.debug("message(%d) conv id(%d) != id(%d), getting parent", msg.getId(), msg.getConversationId(), id);
sameId = false;
item = msg.getParent();
if (item == null) {
ZimbraLog.mailbox.warn("got null parent for message id [%d] conv id [%d] (before condition [%d]) != id [%d]. equality? [%s:%s] in dumpster? [%s]", msg.getId(), msg.getConversationId(), convId, id, convId == id, msg.getConversationId() == id, msg.inDumpster());
}
} else {
ZimbraLog.mailbox.debug("returning normal virtual conv");
item = new VirtualConversation(this, msg);
}
} else {
// cache miss, so fetch from the database
item = MailItem.getById(this, id, type);
}
if (item == null) {
ZimbraLog.mailbox.warn("item is null for id [%d] in mailbox [%d]. Virtual conv? [%s] cachedMsg? [%s] sameId? [%s]", id, this.mId, virtualConv, cachedMsg, sameId);
throw MailItem.noSuchItem(id, type);
}
return item;
}
Aggregations