use of com.zimbra.cs.redolog.op.RedoableOp in project zm-mailbox by Zimbra.
the class RedoPlayer method scanLog.
/**
* Scans a redo log file. An op that is neither committed nor aborted is
* added to mOpsMap. These are the ops that need to be reattempted during
* crash recovery. If redoCommitted is true, an op is reattempted as soon
* as its COMMIT entry is encountered. This case is for replaying the logs
* during mailbox restore.
* @param logfile
* @param redoCommitted
* @param mboxIDsMap If not null, restrict replay of log entries to
* mailboxes whose IDs are given by the key set of the
* map. Replay is done against mailboxes whose IDs are
* given by the value set of the map. Thus, it is
* possible to replay operations from one mailbox in
* a different mailbox.
* @param startTime Only process ops whose prepare time is at or later than
* this time.
* @param endTime Only process ops whose commit time is before (but not
* at) this time.
* @param ignoreCommitsAtOrAfter Ops that were committed at or after this timestamp are ignored.
* They will not be replayed even when redoCommitted=true. They will
* be considered uncommitted, and thus will become eligible for replay
* during crash recovery. For uses other than crash recovery, pass
* Long.MAX_VALUE to not ignore any committed ops.
* @throws IOException
*/
private void scanLog(File logfile, boolean redoCommitted, Map<Integer, Integer> mboxIDsMap, long startTime, long endTime, long ignoreCommitsAtOrAfter) throws IOException, ServiceException {
FileLogReader logReader = new FileLogReader(logfile, mWritable);
logReader.open();
long lastPosition = 0;
// Read all ops in redo log, discarding those with commit/abort entries.
try {
RedoableOp op = null;
while ((op = logReader.getNextOp()) != null) {
lastPosition = logReader.position();
if (ZimbraLog.redolog.isDebugEnabled())
ZimbraLog.redolog.debug("Read: " + op);
processOp(op, redoCommitted, mboxIDsMap, startTime, endTime, ignoreCommitsAtOrAfter);
}
} catch (IOException e) {
// The IOException could be a real I/O problem or it could mean
// there was a server crash previously and there were half-written
// log entries. We can't really tell which case it is, so just
// assume the second case and truncate the file after the last
// successfully read item.
ZimbraLog.redolog.warn("IOException while reading redolog file", e);
long size = logReader.getSize();
if (lastPosition < size) {
long diff = size - lastPosition;
String msg = "There were " + diff + " bytes of junk data at the end of " + logfile.getAbsolutePath() + ".";
if (mWritable) {
ZimbraLog.redolog.warn(msg + " File will be truncated to " + lastPosition + " bytes.");
logReader.truncate(lastPosition);
} else
ZimbraLog.redolog.warn(msg);
}
} finally {
logReader.close();
}
}
use of com.zimbra.cs.redolog.op.RedoableOp in project zm-mailbox by Zimbra.
the class RedoLogVerify method scanLog.
public boolean scanLog(File logfile) throws IOException {
boolean good = false;
FileLogReader logReader = new FileLogReader(logfile, false);
logReader.open();
if (!mParams.quiet) {
FileHeader header = logReader.getHeader();
mOut.println("HEADER");
mOut.println("------");
mOut.print(header);
mOut.println("------");
}
boolean hasMailboxIdsFilter = !mParams.mboxIds.isEmpty();
RedoableOp op = null;
long lastPosition = 0;
long lastOpStartOffset = 0;
try {
while ((op = logReader.getNextOp()) != null) {
lastOpStartOffset = logReader.getLastOpStartOffset();
lastPosition = logReader.position();
if (hasMailboxIdsFilter) {
int mboxId = op.getMailboxId();
if (op instanceof StoreIncomingBlob) {
List<Integer> list = ((StoreIncomingBlob) op).getMailboxIdList();
if (list != null) {
boolean match = false;
for (Integer mid : list) {
if (mParams.mboxIds.contains(mid)) {
match = true;
break;
}
}
if (!match)
continue;
}
// If list==null, it's a store incoming blob op targeted at unknown set of mailboxes.
// It applies to our filtered mailboxes.
} else if (!mParams.mboxIds.contains(mboxId)) {
continue;
}
}
if (!mParams.quiet) {
printOp(mOut, op, mParams.hideOffset, lastOpStartOffset, lastPosition - lastOpStartOffset);
if (mParams.showBlob) {
InputStream dataStream = op.getAdditionalDataStream();
if (dataStream != null) {
mOut.println("<START OF BLOB>");
ByteUtil.copy(dataStream, true, mOut, false);
mOut.println();
mOut.println("<END OF BLOB>");
}
}
}
}
good = true;
} catch (IOException e) {
// The IOException could be a real I/O problem or it could mean
// there was a server crash previously and there were half-written
// log entries.
mOut.println();
mOut.printf("Error while parsing data starting at offset 0x%08x", lastPosition);
mOut.println();
long size = logReader.getSize();
long diff = size - lastPosition;
mOut.printf("%d bytes remaining in the file", diff);
mOut.println();
mOut.println();
if (op != null) {
mOut.println("Last suceessfully parsed redo op:");
printOp(mOut, op, false, lastOpStartOffset, lastPosition - lastOpStartOffset);
mOut.println();
}
// hexdump data around the bad bytes
int bytesPerLine = 16;
int linesBefore = 10;
int linesAfter = 10;
long startPos = Math.max(lastPosition - (lastPosition % bytesPerLine) - linesBefore * bytesPerLine, 0);
int count = (int) Math.min((linesBefore + linesAfter + 1) * bytesPerLine, lastPosition - startPos + diff);
RandomAccessFile raf = null;
try {
raf = new RandomAccessFile(logfile, "r");
raf.seek(startPos);
byte[] buf = new byte[count];
raf.read(buf, 0, count);
mOut.printf("Data near error offset %08x:", lastPosition);
mOut.println();
hexdump(mOut, buf, 0, count, startPos, lastPosition);
mOut.println();
} catch (IOException eh) {
mOut.println("Error opening log file " + logfile.getAbsolutePath() + " for hexdump");
eh.printStackTrace(mOut);
} finally {
if (raf != null)
raf.close();
}
throw e;
} finally {
logReader.close();
}
return good;
}
use of com.zimbra.cs.redolog.op.RedoableOp in project zm-mailbox by Zimbra.
the class Mailbox method endTransaction.
/**
* Be very careful when changing code in this method. The order of almost
* every line of code is important to ensure correct redo logging and crash
* recovery.
*
* @param success true to commit the transaction, false to rollback
* @throws ServiceException error
*/
protected void endTransaction(boolean success) throws ServiceException {
assert !Thread.holdsLock(this) : "Use MailboxLock";
if (lock.isUnlocked()) {
ZimbraLog.mailbox.warn("transaction canceled because of lock failure");
assert (!success);
return;
}
// blob and index to delete
PendingDelete deletes = null;
// blob to delete for failure cases
List<Object> rollbackDeletes = null;
try {
if (!currentChange().isActive()) {
// would like to throw here, but it might cover another
// exception...
ZimbraLog.mailbox.warn("cannot end a transaction when not inside a transaction", new Exception());
return;
}
if (!currentChange().endChange()) {
return;
}
ServiceException exception = null;
if (success) {
List<IndexItemEntry> indexItems = currentChange().indexItems;
if (!indexItems.isEmpty()) {
assert (currentChange().writeChange);
//TODO: See bug 15072 - we need to clear mCurrentChange.indexItems (it is stored in a temporary) here,
// just in case item.reindex() recurses into a new transaction...
currentChange().indexItems = new ArrayList<IndexItemEntry>();
index.add(indexItems);
}
// update mailbox size, folder unread/message counts
try {
snapshotCounts();
} catch (ServiceException e) {
exception = e;
success = false;
}
}
DbConnection conn = currentChange().conn;
// transaction, so no redo cleanup is necessary.
if (!success) {
DbPool.quietRollback(conn);
rollbackDeletes = rollbackCache(currentChange());
if (exception != null) {
throw exception;
}
return;
}
RedoableOp redoRecorder = currentChange().recorder;
boolean needRedo = needRedo(currentChange().octxt, redoRecorder);
// Log the change redo record for main transaction.
if (redoRecorder != null && needRedo) {
redoRecorder.log(true);
}
boolean dbCommitSuccess = false;
try {
// Commit the main transaction in database.
if (conn != null) {
try {
conn.commit();
} catch (Throwable t) {
// Any exception during database commit is a disaster
// because we don't know if the change is committed or
// not. Force the server to abort. Next restart will
// redo the operation to ensure the change is made and
// committed. (bug 2121)
Zimbra.halt("Unable to commit database transaction. Forcing server to abort.", t);
}
}
dbCommitSuccess = true;
} finally {
if (!dbCommitSuccess) {
// recovery will try to redo the operation.
if (needRedo) {
if (redoRecorder != null) {
redoRecorder.abort();
}
}
DbPool.quietRollback(conn);
rollbackDeletes = rollbackCache(currentChange());
return;
}
}
if (needRedo) {
// case would result in a redo error, and the second case would index the wrong value.
if (redoRecorder != null) {
if (currentChange().dirty != null && !currentChange().dirty.changedTypes.isEmpty()) {
// if an "all accounts" waitset is active, and this change has an appropriate type,
// then we'll need to set a commit-callback
AllAccountsRedoCommitCallback cb = AllAccountsRedoCommitCallback.getRedoCallbackIfNecessary(getAccountId(), currentChange().dirty.changedTypes);
if (cb != null) {
redoRecorder.setCommitCallback(cb);
}
}
redoRecorder.commit();
}
}
boolean changeMade = currentChange().changeId != MailboxChange.NO_CHANGE;
// keep a reference for cleanup
deletes = currentChange().deletes;
// deletes outside the lock
// We are finally done with database and redo commits. Cache update
// comes last.
commitCache(currentChange());
// down in its call stack.
if (changeMade) {
index.maybeIndexDeferredItems();
}
} finally {
lock.release();
// entail a blocking network operation
if (deletes != null) {
if (!deletes.indexIds.isEmpty()) {
// delete any index entries associated with items deleted from db
index.delete(deletes.indexIds);
}
if (deletes.blobs != null) {
// delete any blobs associated with items deleted from db/index
StoreManager sm = StoreManager.getInstance();
for (MailboxBlob blob : deletes.blobs) {
sm.quietDelete(blob);
}
}
}
if (rollbackDeletes != null) {
StoreManager sm = StoreManager.getInstance();
for (Object obj : rollbackDeletes) {
if (obj instanceof MailboxBlob) {
sm.quietDelete((MailboxBlob) obj);
} else if (obj instanceof Blob) {
sm.quietDelete((Blob) obj);
}
}
}
}
}
use of com.zimbra.cs.redolog.op.RedoableOp 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 (MailItem item : currentChange().dirty.created.values()) {
if (item instanceof Folder) {
foldersTagsDirty = true;
if (item.getSize() != 0) {
((Folder) item).saveFolderCounts(false);
}
} else if (item instanceof Tag) {
foldersTagsDirty = true;
if (item.isUnread()) {
((Tag) item).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 (MailItem item : currentChange().dirty.created.values()) {
DbMailItem.consistencyCheck(item, item.mData, item.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());
}
}
}
}
}
use of com.zimbra.cs.redolog.op.RedoableOp 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);
}
}
}
}
}
}
}
}
Aggregations