Search in sources :

Example 36 with ICalTimeZone

use of com.zimbra.common.calendar.ICalTimeZone in project zm-mailbox by Zimbra.

the class Mailbox method fixCalendarItemTZ.

/**
     * Fix up timezone definitions in an appointment/task.  Fixup is
     * required when governments change the daylight savings policy.
     *
     * @param fixupRules rules specifying which timezones to fix and how
     * @return number of timezone objects that were modified
     */
public int fixCalendarItemTZ(OperationContext octxt, int calItemId, TimeZoneFixupRules fixupRules) throws ServiceException {
    FixCalendarItemTZ redoRecorder = new FixCalendarItemTZ(getId(), calItemId);
    boolean success = false;
    try {
        beginTransaction("fixCalendarItemTimeZone2", octxt, redoRecorder);
        CalendarItem calItem = getCalendarItemById(octxt, calItemId);
        Map<String, ICalTimeZone> replaced = new HashMap<String, ICalTimeZone>();
        int numFixed = fixupRules.fixCalendarItem(calItem, replaced);
        if (numFixed > 0) {
            ZimbraLog.calendar.info("Fixed " + numFixed + " timezone entries in calendar item " + calItem.getId());
            redoRecorder.setReplacementMap(replaced);
            markItemModified(calItem, Change.CONTENT | Change.INVITE);
            calItem.snapshotRevision();
            calItem.saveMetadata();
            // Need to uncache and refetch the item because there are fields
            // in the appointment/task that reference the old, pre-fix version
            // of the timezones.  We can either visit them all and update them,
            // or simply invalidate the calendar item and refetch it.
            uncacheItem(calItemId);
            calItem = getCalendarItemById(octxt, calItemId);
            success = true;
            Callback cb = CalendarItem.getCallback();
            if (cb != null) {
                cb.modified(calItem);
            }
        }
        return numFixed;
    } finally {
        endTransaction(success);
    }
}
Also used : SetCalendarItem(com.zimbra.cs.redolog.op.SetCalendarItem) AllAccountsRedoCommitCallback(com.zimbra.cs.session.AllAccountsRedoCommitCallback) Callback(com.zimbra.cs.mailbox.CalendarItem.Callback) ConcurrentHashMap(java.util.concurrent.ConcurrentHashMap) ConcurrentLinkedHashMap(com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap) HashMap(java.util.HashMap) FixCalendarItemTZ(com.zimbra.cs.redolog.op.FixCalendarItemTZ) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone) RefreshMountpoint(com.zimbra.cs.redolog.op.RefreshMountpoint) TargetConstraint(com.zimbra.cs.mailbox.MailItem.TargetConstraint) CreateMountpoint(com.zimbra.cs.redolog.op.CreateMountpoint)

Example 37 with ICalTimeZone

use of com.zimbra.common.calendar.ICalTimeZone in project zm-mailbox by Zimbra.

the class CompleteTaskInstance method handle.

@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
    ZimbraSoapContext zsc = getZimbraSoapContext(context);
    Mailbox mbox = getRequestedMailbox(zsc);
    OperationContext octxt = getOperationContext(zsc, context);
    ItemId iid = new ItemId(request.getAttribute(MailConstants.A_ID), zsc);
    Element exceptElem = request.getElement(MailConstants.E_CAL_EXCEPTION_ID);
    mbox.lock.lock();
    try {
        CalendarItem calItem = mbox.getCalendarItemById(octxt, iid.getId());
        if (calItem == null) {
            throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
        }
        if (!(calItem instanceof Task)) {
            throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Calendar item is not a task");
        }
        Invite inv = calItem.getDefaultInviteOrNull();
        if (inv == null) {
            throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "No default invite found");
        }
        if (!inv.isRecurrence()) {
            throw ServiceException.INVALID_REQUEST("Task is not a recurring task", null);
        }
        ParsedDateTime recurStart = inv.getStartTime();
        if (recurStart == null) {
            throw ServiceException.INVALID_REQUEST("Recurring task is missing start time", null);
        }
        // the instance being marked complete
        TimeZoneMap tzmap = inv.getTimeZoneMap();
        Element tzElem = request.getOptionalElement(MailConstants.E_CAL_TZ);
        ICalTimeZone tz = null;
        if (tzElem != null) {
            tz = CalendarUtils.parseTzElement(tzElem);
            tzmap.add(tz);
        }
        ParsedDateTime exceptDt = CalendarUtils.parseDateTime(exceptElem, tzmap);
        if (exceptDt.getUtcTime() != recurStart.getUtcTime()) {
            throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
        }
        // Create a new single-instance task for completed date.
        Invite completed = createCompletedInstanceInvite(inv, exceptDt);
        mbox.addInvite(octxt, completed, calItem.getFolderId());
        // Update recurrence's start date to the next instance start date.
        long oldStart = recurStart.getUtcTime();
        long newStart = -1;
        Collection<Instance> instances = calItem.expandInstances(oldStart, Long.MAX_VALUE, false);
        for (Instance inst : instances) {
            if (inst.getStart() > oldStart) {
                newStart = inst.getStart();
                break;
            }
        }
        if (newStart != -1) {
            // Update DTSTART to newStart.
            ParsedDateTime newStartDt = ParsedDateTime.fromUTCTime(newStart);
            newStartDt.toTimeZone(inv.getStartTime().getTimeZone());
            newStartDt.setHasTime(recurStart.hasTime());
            // Update DUE.
            ParsedDuration dur = inv.getEffectiveDuration();
            if (dur != null) {
                ParsedDateTime due = newStartDt.add(dur);
                inv.setDtEnd(due);
            }
            inv.setDtStart(newStartDt);
            inv.setSeqNo(inv.getSeqNo() + 1);
            inv.setDtStamp(System.currentTimeMillis());
            mbox.addInvite(octxt, inv, calItem.getFolderId());
        } else {
            // No more instance left.  Delete the recurring task.
            mbox.delete(octxt, calItem.getId(), calItem.getType());
        }
    } finally {
        mbox.lock.release();
    }
    // response
    Element response = getResponseElement(zsc);
    return response;
}
Also used : OperationContext(com.zimbra.cs.mailbox.OperationContext) Task(com.zimbra.cs.mailbox.Task) Instance(com.zimbra.cs.mailbox.CalendarItem.Instance) ParsedDuration(com.zimbra.common.calendar.ParsedDuration) Element(com.zimbra.common.soap.Element) ItemId(com.zimbra.cs.service.util.ItemId) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) Mailbox(com.zimbra.cs.mailbox.Mailbox) ZimbraSoapContext(com.zimbra.soap.ZimbraSoapContext) TimeZoneMap(com.zimbra.common.calendar.TimeZoneMap) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) Invite(com.zimbra.cs.mailbox.calendar.Invite) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Example 38 with ICalTimeZone

use of com.zimbra.common.calendar.ICalTimeZone in project zm-mailbox by Zimbra.

the class SendInviteReply method handle.

@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
    ZimbraSoapContext zsc = getZimbraSoapContext(context);
    Mailbox mbox = getRequestedMailbox(zsc);
    Account acct = getRequestedAccount(zsc);
    Account authAcct = getAuthenticatedAccount(zsc);
    boolean isAdmin = zsc.isUsingAdminPrivileges();
    OperationContext octxt = getOperationContext(zsc, context);
    boolean onBehalfOf = isOnBehalfOfRequest(zsc);
    ItemId iid = new ItemId(request.getAttribute(MailConstants.A_ID), zsc);
    int compNum = (int) request.getAttributeLong(MailConstants.A_CAL_COMPONENT_NUM);
    String verbStr = request.getAttribute(MailConstants.A_VERB);
    Verb verb = CalendarMailSender.parseVerb(verbStr);
    boolean isDecline = CalendarMailSender.VERB_DECLINE.equals(verb);
    boolean updateOrg = request.getAttributeBool(MailConstants.A_CAL_UPDATE_ORGANIZER, true);
    // Get the identity/persona being used in the reply.  It is set at the request level, but
    // let's also look for it in the <m> child element too, because that is the precedent in
    // the SendMsg request.  For SendInviteReply we have to insist it at request level because
    // <m> is an optional element.
    String identityId = request.getAttribute(MailConstants.A_IDENTITY_ID, null);
    if (identityId == null) {
        Element msgElem = request.getOptionalElement(MailConstants.E_MSG);
        if (msgElem != null)
            identityId = msgElem.getAttribute(MailConstants.A_IDENTITY_ID, null);
    }
    Element response = getResponseElement(zsc);
    boolean intendedForMe = true;
    Invite oldInv = null;
    int calItemId;
    int inviteMsgId;
    CalendarItem calItem = null;
    boolean wasInTrash = false;
    // calendar item (id="aaaa-nnnn") --- work in both cases
    if (iid.hasSubpart()) {
        // directly accepting the calendar item
        calItemId = iid.getId();
        inviteMsgId = iid.getSubpartId();
        calItem = safeGetCalendarItemById(mbox, octxt, iid.getId());
        if (calItem == null)
            throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
        oldInv = calItem.getInvite(inviteMsgId, compNum);
    } else {
        // accepting the message: go find the calendar item and then the invite
        inviteMsgId = iid.getId();
        Message msg = mbox.getMessageById(octxt, inviteMsgId);
        Message.CalendarItemInfo info = msg.getCalendarItemInfo(compNum);
        if (info == null)
            throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
        String intendedFor = msg.getCalendarIntendedFor();
        Account intendedAcct = null;
        if (intendedFor != null) {
            try {
                InternetAddress intendedForAddr = new JavaMailInternetAddress(intendedFor);
                intendedAcct = Provisioning.getInstance().get(AccountBy.name, intendedForAddr.getAddress());
            } catch (AddressException e) {
                throw ServiceException.INVALID_REQUEST("The intended account " + intendedFor + " is invalid", e);
            }
            if (intendedAcct == null) {
                throw ServiceException.INVALID_REQUEST("The intended account " + intendedFor + " was not found", null);
            }
            // Special case: intended account = me.
            if (intendedAcct.equals(mbox.getAccount()))
                intendedAcct = null;
            else
                intendedForMe = false;
        }
        if (intendedAcct != null) {
            // trace logging: let's just indicate we're replying to a remote appointment
            ZimbraLog.calendar.info("<SendInviteReply> (remote mbox) id=%s, verb=%s, notifyOrg=%s", new ItemIdFormatter(zsc).formatItemId(iid), verb.toString(), Boolean.toString(updateOrg));
            // Replying to a remote appointment
            calItem = null;
            calItemId = 0;
            ZMailbox zmbx = getRemoteZMailbox(octxt, authAcct, intendedAcct);
            // Try to add the appointment to remote mailbox.
            AddInviteResult addInviteResult = sendAddInvite(zmbx, octxt, msg);
            if (addInviteResult == null)
                throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
            // Forward the reply request.
            remoteSendInviteReply(zmbx, request, addInviteResult);
        } else {
            // Replying to a local appointment
            if (info.getInvite() != null) {
                calItem = mbox.getCalendarItemByUid(octxt, info.getInvite().getUid());
                wasInTrash = calItem != null && calItem.inTrash();
                if (calItem != null && !wasInTrash) {
                    Invite newInv = info.getInvite();
                    // If appointment exists, check if our invite has been outdated.
                    Invite curr = calItem.getInvite(newInv.getRecurId());
                    if (curr != null && !newInv.isSameOrNewerVersion(curr))
                        throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
                }
                Invite inv = info.getInvite().newCopy();
                Invite.setDefaultAlarm(inv, acct);
                inv.setMailItemId(inviteMsgId);
                // (TODO: Is it better to delete the existing appointment/instance when declining?)
                if (calItem != null || !isDecline) {
                    // Add the invite.  This will either create or update the appointment.
                    int folder;
                    boolean untrashing = wasInTrash && !isDecline;
                    if (calItem == null || untrashing) {
                        // If appointment/task doesn't exist, create in default folder.
                        // If it exists but is in Trash and is not a decline, move it out of Trash.
                        // If it's in trash and we're declining, leave it in trash.
                        folder = inv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
                    } else {
                        folder = calItem.getFolderId();
                    }
                    ParsedMessage pm = new ParsedMessage(msg.getMimeMessage(false), false);
                    AddInviteData aid = mbox.addInvite(octxt, inv, folder, pm, false, untrashing, true);
                    if (aid == null)
                        throw ServiceException.FAILURE("Could not create/update calendar item", null);
                    calItemId = aid.calItemId;
                    // Refetch updated item.
                    calItem = safeGetCalendarItemById(mbox, octxt, aid.calItemId);
                    if (calItem == null)
                        throw ServiceException.FAILURE("Could not refetch created/updated calendar item", null);
                } else {
                    calItemId = 0;
                }
                oldInv = inv;
            } else if (info.calItemCreated()) {
                // legacy case (before we added Invite info to Message metadata)
                calItem = safeGetCalendarItemById(mbox, octxt, info.getCalendarItemId());
                if (calItem == null)
                    throw ServiceException.FAILURE("Missing invite data", null);
                // Must be for this mailbox
                calItemId = info.getCalendarItemId().getId();
                wasInTrash = calItem.inTrash();
                oldInv = calItem.getInvite(inviteMsgId, compNum);
            } else {
                throw ServiceException.FAILURE("Missing invite data", null);
            }
        }
    }
    if (intendedForMe) {
        if (oldInv == null)
            throw MailServiceException.INVITE_OUT_OF_DATE(iid.toString());
        if (calItem != null && (mbox.getEffectivePermissions(octxt, calItemId, MailItem.Type.UNKNOWN) & ACL.RIGHT_ACTION) == 0) {
            throw ServiceException.PERM_DENIED("You do not have ACTION rights for CalendarItem " + calItemId);
        }
        // check if invite organizer requested rsvp or not
        updateOrg = updateOrg && oldInv.getRsvp();
        // Don't allow creating/editing a private appointment on behalf of another user,
        // unless that other user is a calendar resource.
        boolean allowPrivateAccess = calItem != null ? calItem.allowPrivateAccess(authAcct, isAdmin) : true;
        boolean isCalendarResource = acct instanceof CalendarResource;
        if (!allowPrivateAccess && !oldInv.isPublic() && !isCalendarResource)
            throw ServiceException.PERM_DENIED("Cannot reply to a private appointment/task on behalf of another user");
        // see if there is a specific Exception being referenced by this reply...
        Element exc = request.getOptionalElement(MailConstants.E_CAL_EXCEPTION_ID);
        ParsedDateTime exceptDt = null;
        if (exc != null) {
            TimeZoneMap tzmap = oldInv.getTimeZoneMap();
            Element tzElem = request.getOptionalElement(MailConstants.E_CAL_TZ);
            ICalTimeZone tz = null;
            if (tzElem != null) {
                tz = CalendarUtils.parseTzElement(tzElem);
                tzmap.add(tz);
            }
            exceptDt = CalendarUtils.parseDateTime(exc, tzmap);
        } else if (oldInv.hasRecurId()) {
            exceptDt = oldInv.getRecurId().getDt();
        }
        // trace logging
        String calItemIdStr = calItem != null ? Integer.toString(calItem.getId()) : "none";
        String folderIdStr = calItem != null ? Integer.toString(calItem.getFolderId()) : "none";
        if (exceptDt == null)
            ZimbraLog.calendar.info("<SendInviteReply> id=%s, folderId=%s, verb=%s, notifyOrg=%s, subject=\"%s\", UID=%s", calItemIdStr, folderIdStr, verb.toString(), Boolean.toString(updateOrg), oldInv.isPublic() ? oldInv.getName() : "(private)", oldInv.getUid());
        else
            ZimbraLog.calendar.info("<SendInviteReply> id=%s, folderId=%s, verb=%s, notifyOrg=%s, subject=\"%s\", UID=%s, recurId=%s", calItemIdStr, folderIdStr, verb.toString(), Boolean.toString(updateOrg), oldInv.isPublic() ? oldInv.getName() : "(private)", oldInv.getUid(), exceptDt.getUtcString());
        // exception instance first.  Then reply to it.
        if (calItem != null && oldInv.isRecurrence() && exceptDt != null) {
            Invite localException = oldInv.makeInstanceInvite(exceptDt);
            long now = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
            localException.setDtStamp(now);
            String partStat = verb.getXmlPartStat();
            localException.setPartStat(partStat);
            ZAttendee at = localException.getMatchingAttendee(acct, identityId);
            if (at != null)
                at.setPartStat(partStat);
            // Carry over the MimeMessage/ParsedMessage to preserve any attachments.
            MimeMessage mmInv = calItem.getSubpartMessage(oldInv.getMailItemId());
            ParsedMessage pm = mmInv != null ? new ParsedMessage(mmInv, false) : null;
            int folder;
            boolean untrashing = wasInTrash && !isDecline;
            if (untrashing) {
                // If it exists but is in Trash and is not a decline, move it out of Trash.
                // If it's in trash and we're declining, leave it in trash.
                folder = localException.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
            } else {
                folder = calItem.getFolderId();
            }
            mbox.addInvite(octxt, localException, folder, pm, true, untrashing, true);
            // Refetch the updated calendar item and set oldInv to refetched local exception instance.
            calItem = safeGetCalendarItemById(mbox, octxt, calItemId);
            if (calItem == null)
                throw MailServiceException.NO_SUCH_CALITEM(iid.toString(), "Could not find calendar item");
            oldInv = calItem.getInvite(new RecurId(exceptDt, RecurId.RANGE_NONE));
        }
        if (updateOrg && oldInv.hasOrganizer()) {
            Locale locale;
            Account organizer = oldInv.getOrganizerAccount();
            if (organizer != null)
                locale = organizer.getLocale();
            else
                locale = !onBehalfOf ? acct.getLocale() : authAcct.getLocale();
            String subject;
            if (!allowPrivateAccess && !oldInv.isPublic())
                subject = L10nUtil.getMessage(MsgKey.calendarSubjectWithheld, locale);
            else
                subject = oldInv.getName();
            String replySubject = CalendarMailSender.getReplySubject(verb, subject, locale);
            CalSendData csd = new CalSendData();
            csd.mOrigId = new ItemId(mbox, oldInv.getMailItemId());
            csd.mReplyType = MailSender.MSGTYPE_REPLY;
            csd.mInvite = CalendarMailSender.replyToInvite(acct, identityId, authAcct, onBehalfOf, allowPrivateAccess, oldInv, verb, replySubject, exceptDt);
            ZVCalendar iCal = csd.mInvite.newToICalendar(true);
            ParseMimeMessage.MimeMessageData parsedMessageData = new ParseMimeMessage.MimeMessageData();
            // did they specify a custom <m> message?  If so, then we don't have to build one...
            Element msgElem = request.getOptionalElement(MailConstants.E_MSG);
            if (msgElem != null) {
                String text = ParseMimeMessage.getTextPlainContent(msgElem);
                String html = ParseMimeMessage.getTextHtmlContent(msgElem);
                iCal.addDescription(text, html);
                MimeBodyPart[] mbps = new MimeBodyPart[1];
                mbps[0] = CalendarMailSender.makeICalIntoMimePart(iCal);
                // the <inv> element is *NOT* allowed -- we always build it manually
                // based on the params to the <SendInviteReply> and stick it in the
                // mbps (additionalParts) parameter...
                csd.mMm = ParseMimeMessage.parseMimeMsgSoap(zsc, octxt, mbox, msgElem, mbps, ParseMimeMessage.NO_INV_ALLOWED_PARSER, parsedMessageData);
            } else {
                // build a default "Accepted" response
                if (!(acct instanceof CalendarResource)) {
                    csd.mMm = CalendarMailSender.createDefaultReply(acct, identityId, authAcct, identityId, isAdmin, onBehalfOf, calItem, oldInv, null, replySubject, verb, null, iCal);
                } else {
                    // different template for calendar resources
                    RecurId rid = oldInv.getRecurId();
                    ParsedDateTime ridDt = rid != null ? rid.getDt() : null;
                    Invite replyInv = CalendarMailSender.replyToInvite(acct, authAcct, onBehalfOf, allowPrivateAccess, oldInv, verb, replySubject, ridDt);
                    MimeMessage mmInv = calItem.getSubpartMessage(oldInv.getMailItemId());
                    csd.mMm = CalendarMailSender.createResourceAutoReply(octxt, identityId, identityId, mbox, verb, false, null, calItem, oldInv, new Invite[] { replyInv }, mmInv, true);
                }
            }
            int apptFolderId;
            if (calItem != null)
                apptFolderId = calItem.getFolderId();
            else
                apptFolderId = oldInv.isTodo() ? Mailbox.ID_FOLDER_TASKS : Mailbox.ID_FOLDER_CALENDAR;
            MailSendQueue sendQueue = new MailSendQueue();
            try {
                sendCalendarMessage(zsc, octxt, apptFolderId, acct, mbox, csd, response, sendQueue);
            } finally {
                sendQueue.send();
            }
        }
        RecurId recurId = null;
        if (exceptDt != null) {
            recurId = new RecurId(exceptDt, RecurId.RANGE_NONE);
        }
        ZAttendee me = oldInv.getMatchingAttendee(acct);
        String cnStr = null;
        String addressStr = acct.getName();
        String role = IcalXmlStrMap.ROLE_OPT_PARTICIPANT;
        int seqNo = oldInv.getSeqNo();
        long dtStamp = oldInv.getDTStamp();
        if (me != null) {
            if (me.hasCn()) {
                cnStr = me.getCn();
            }
            addressStr = me.getAddress();
            if (me.hasRole()) {
                role = me.getRole();
            }
        }
        if (calItem != null)
            mbox.modifyPartStat(octxt, calItemId, recurId, cnStr, addressStr, null, role, verb.getXmlPartStat(), Boolean.FALSE, seqNo, dtStamp);
    }
    // move the invite to the Trash if the user wants it
    if (deleteInviteOnReply(acct)) {
        try {
            if (onBehalfOf) {
                // HACK: Run the move in the context of the organizer
                // mailbox because the authenticated account doesn't
                // have rights on Inbox and Trash folders.
                octxt = new OperationContext(mbox);
            }
            mbox.move(octxt, inviteMsgId, MailItem.Type.MESSAGE, Mailbox.ID_FOLDER_TRASH);
        } catch (MailServiceException.NoSuchItemException nsie) {
            ZimbraLog.calendar.debug("can't move nonexistent invite to Trash: " + inviteMsgId);
        }
    }
    return response;
}
Also used : Locale(java.util.Locale) Account(com.zimbra.cs.account.Account) InternetAddress(javax.mail.internet.InternetAddress) JavaMailInternetAddress(com.zimbra.common.mime.shim.JavaMailInternetAddress) Message(com.zimbra.cs.mailbox.Message) MimeMessage(javax.mail.internet.MimeMessage) ParsedMessage(com.zimbra.cs.mime.ParsedMessage) ItemIdFormatter(com.zimbra.cs.service.util.ItemIdFormatter) AddInviteData(com.zimbra.cs.mailbox.Mailbox.AddInviteData) Element(com.zimbra.common.soap.Element) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) ItemId(com.zimbra.cs.service.util.ItemId) CalendarItem(com.zimbra.cs.mailbox.CalendarItem) ZVCalendar(com.zimbra.common.calendar.ZCalendar.ZVCalendar) ZMailbox(com.zimbra.client.ZMailbox) Mailbox(com.zimbra.cs.mailbox.Mailbox) ZMailbox(com.zimbra.client.ZMailbox) MimeMessage(javax.mail.internet.MimeMessage) Verb(com.zimbra.cs.mailbox.calendar.CalendarMailSender.Verb) AddressException(javax.mail.internet.AddressException) TimeZoneMap(com.zimbra.common.calendar.TimeZoneMap) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) MailServiceException(com.zimbra.cs.mailbox.MailServiceException) CalendarResource(com.zimbra.cs.account.CalendarResource) OperationContext(com.zimbra.cs.mailbox.OperationContext) ParsedMessage(com.zimbra.cs.mime.ParsedMessage) ZimbraSoapContext(com.zimbra.soap.ZimbraSoapContext) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) JavaMailInternetAddress(com.zimbra.common.mime.shim.JavaMailInternetAddress) MimeBodyPart(javax.mail.internet.MimeBodyPart) Invite(com.zimbra.cs.mailbox.calendar.Invite) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Example 39 with ICalTimeZone

use of com.zimbra.common.calendar.ICalTimeZone in project zm-mailbox by Zimbra.

the class Appointment method processPartStat.

@Override
protected String processPartStat(Invite invite, MimeMessage mmInv, boolean forCreate, String defaultPartStat) throws ServiceException {
    Mailbox mbox = getMailbox();
    OperationContext octxt = mbox.getOperationContext();
    CreateCalendarItemPlayer player = octxt != null ? (CreateCalendarItemPlayer) octxt.getPlayer() : null;
    long opTime = octxt != null ? octxt.getTimestamp() : System.currentTimeMillis();
    Account account = getMailbox().getAccount();
    boolean onBehalfOf = false;
    Account authAcct = account;
    if (octxt != null) {
        Account authuser = octxt.getAuthenticatedUser();
        if (authuser != null) {
            onBehalfOf = !account.getId().equalsIgnoreCase(authuser.getId());
            if (onBehalfOf)
                authAcct = authuser;
        }
    }
    boolean asAdmin = octxt != null ? octxt.isUsingAdminPrivileges() : false;
    boolean allowPrivateAccess = allowPrivateAccess(authAcct, asAdmin);
    String partStat = defaultPartStat;
    if (player != null) {
        String p = player.getCalendarItemPartStat();
        if (p != null)
            partStat = p;
    }
    // See if we have RSVP=FALSE for the attendee.  Let's assume RSVP was requested unless it is
    // explicitly set to FALSE.
    boolean rsvpRequested = true;
    ZAttendee attendee = invite.getMatchingAttendee(account);
    if (attendee != null) {
        Boolean rsvp = attendee.getRsvp();
        if (rsvp != null)
            rsvpRequested = rsvp.booleanValue();
    }
    RedoLogProvider redoProvider = RedoLogProvider.getInstance();
    // Don't send reply emails if we're not on master (in redo-driven master/replica setup).
    // Don't send reply emails if we're replaying redo for reasons other than crash recovery.
    // In other words, we DO want to send emails during crash recovery, because we're completing
    // an interrupted transaction.  But we don't send emails during redo reply phase of restore.
    // Also don't send emails for cancel invites.  (Organizer doesn't expect reply for cancels.)
    // And don't send emails for task requests.
    // Don't send reply emails from a system account. (e.g. archiving, galsync, ham/spam)
    boolean needReplyEmail = rsvpRequested && redoProvider.isMaster() && (player == null || redoProvider.getRedoLogManager().getInCrashRecovery()) && invite.hasOrganizer() && !invite.isCancel() && !invite.isTodo() && !account.isIsSystemResource();
    if (invite.isOrganizer()) {
        // Organizer always accepts.
        partStat = IcalXmlStrMap.PARTSTAT_ACCEPTED;
    } else if (account instanceof CalendarResource && octxt == null) {
        // Auto accept/decline processing should only occur during email delivery.  In particular,
        // don't do it if we're here during ics import.  We're in email delivery if octxt == null.
        // (There needs to be a better way to determine that...)
        boolean replySent = false;
        CalendarResource resource = (CalendarResource) account;
        Locale lc;
        Account organizer = invite.getOrganizerAccount();
        if (organizer != null)
            lc = organizer.getLocale();
        else
            lc = resource.getLocale();
        if (resource.autoAcceptDecline() || resource.autoDeclineIfBusy() || resource.autoDeclineRecurring()) {
            boolean replyListUpdated = false;
            // If auto-accept is enabled, assume it'll be accepted until it gets declined.
            if (resource.autoAcceptDecline())
                partStat = IcalXmlStrMap.PARTSTAT_ACCEPTED;
            if (isRecurring() && resource.autoDeclineRecurring()) {
                // Decline because resource is configured to decline all recurring appointments.
                partStat = IcalXmlStrMap.PARTSTAT_DECLINED;
                if (needReplyEmail) {
                    String reason = L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonRecurring, lc);
                    Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_DECLINE);
                    CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, false, reason + "\r\n", this, invite, new Invite[] { replyInv }, mmInv);
                    replySent = true;
                }
            } else if (resource.autoDeclineIfBusy()) {
                // Auto decline is enabled.  Let's check for conflicts.
                int maxNumConflicts = resource.getMaxNumConflictsAllowed();
                int maxPctConflicts = resource.getMaxPercentConflictsAllowed();
                ConflictCheckResult checkResult = checkAvailability(opTime, invite, maxNumConflicts, maxPctConflicts);
                if (checkResult != null) {
                    List<Conflict> conflicts = checkResult.getConflicts();
                    if (conflicts.size() > 0) {
                        if (invite.isRecurrence() && !checkResult.tooManyConflicts()) {
                            // There are some conflicts, but within resource's allowed limit.
                            if (resource.autoAcceptDecline()) {
                                // Let's accept partially.  (Accept the series and decline conflicting instances.)
                                List<Invite> replyInvites = new ArrayList<Invite>();
                                // the REPLY for the ACCEPT of recurrence series
                                Invite acceptInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_ACCEPT);
                                for (Conflict conflict : conflicts) {
                                    Instance inst = conflict.getInstance();
                                    InviteInfo invInfo = inst.getInviteInfo();
                                    Invite inv = getInvite(invInfo.getMsgId(), invInfo.getComponentId());
                                    RecurId rid = inst.makeRecurId(inv);
                                    // Record the decline status in reply list.
                                    getReplyList().modifyPartStat(resource, rid, null, resource.getName(), null, null, IcalXmlStrMap.PARTSTAT_DECLINED, false, invite.getSeqNo(), opTime);
                                    replyListUpdated = true;
                                    // Make REPLY VEVENT for the declined instance.
                                    Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, inv, rid, CalendarMailSender.VERB_DECLINE);
                                    replyInvites.add(replyInv);
                                }
                                if (needReplyEmail) {
                                    ICalTimeZone tz = chooseReplyTZ(invite);
                                    // Send one email to accept the series.
                                    String declinedInstances = getDeclinedTimesString(octxt, mbox, conflicts, invite.isAllDayEvent(), tz, lc);
                                    String msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclinedInstances, lc) + "\r\n\r\n" + declinedInstances;
                                    CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_ACCEPT, true, msg, this, invite, new Invite[] { acceptInv }, mmInv);
                                    // Send another email to decline instances, all in one email.
                                    String conflictingTimes = getBusyTimesString(octxt, mbox, conflicts, tz, lc, false);
                                    msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclinedInstances, lc) + "\r\n\r\n" + declinedInstances + "\r\n" + L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonConflict, lc) + "\r\n\r\n" + conflictingTimes;
                                    CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, true, msg, this, invite, replyInvites.toArray(new Invite[0]), mmInv);
                                    replySent = true;
                                }
                            } else {
                            // Auto-accept is not enabled.  Auto-decline is enabled, but there weren't
                            // enough conflicting instances to decline outright.  So we just stay
                            // silent and let the human admin deal with it.  This case is rather
                            // ambiguous, and can be avoided by configuring the resource to allow
                            // zero conflicting instance.
                            }
                        } else {
                            // Too many conflicts.  Decline outright.
                            partStat = IcalXmlStrMap.PARTSTAT_DECLINED;
                            if (needReplyEmail) {
                                ICalTimeZone tz = chooseReplyTZ(invite);
                                String msg = L10nUtil.getMessage(MsgKey.calendarResourceDeclineReasonConflict, lc) + "\r\n\r\n" + getBusyTimesString(octxt, mbox, conflicts, tz, lc, checkResult.hasMoreConflicts());
                                Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_DECLINE);
                                CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_DECLINE, false, msg, this, invite, new Invite[] { replyInv }, mmInv);
                                replySent = true;
                            }
                        }
                    }
                }
            }
            if (!replySent && IcalXmlStrMap.PARTSTAT_ACCEPTED.equals(partStat)) {
                if (needReplyEmail) {
                    Invite replyInv = makeReplyInvite(account, authAcct, lc, onBehalfOf, allowPrivateAccess, invite, invite.getRecurId(), CalendarMailSender.VERB_ACCEPT);
                    CalendarMailSender.sendResourceAutoReply(octxt, mbox, true, CalendarMailSender.VERB_ACCEPT, false, null, this, invite, new Invite[] { replyInv }, mmInv);
                }
            }
            // Record the final outcome in the replies list.
            if (IcalXmlStrMap.PARTSTAT_NEEDS_ACTION.equals(partStat)) {
                getReplyList().modifyPartStat(resource, invite.getRecurId(), null, resource.getName(), null, null, partStat, false, invite.getSeqNo(), opTime);
                replyListUpdated = true;
            }
            if (forCreate && replyListUpdated)
                saveMetadata();
        }
    }
    CreateCalendarItemRecorder recorder = (CreateCalendarItemRecorder) mbox.getRedoRecorder();
    recorder.setCalendarItemPartStat(partStat);
    invite.updateMyPartStat(account, partStat);
    if (forCreate) {
        Invite defaultInvite = getDefaultInviteOrNull();
        if (defaultInvite != null && !defaultInvite.equals(invite) && !partStat.equals(defaultInvite.getPartStat())) {
            defaultInvite.updateMyPartStat(account, partStat);
            saveMetadata();
        }
    }
    return partStat;
}
Also used : Locale(java.util.Locale) Account(com.zimbra.cs.account.Account) InviteInfo(com.zimbra.cs.mailbox.calendar.InviteInfo) FBInstance(com.zimbra.cs.fb.FreeBusy.FBInstance) ArrayList(java.util.ArrayList) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) CreateCalendarItemPlayer(com.zimbra.cs.redolog.op.CreateCalendarItemPlayer) CreateCalendarItemRecorder(com.zimbra.cs.redolog.op.CreateCalendarItemRecorder) RedoLogProvider(com.zimbra.cs.redolog.RedoLogProvider) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) CalendarResource(com.zimbra.cs.account.CalendarResource) Invite(com.zimbra.cs.mailbox.calendar.Invite) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Example 40 with ICalTimeZone

use of com.zimbra.common.calendar.ICalTimeZone in project zm-mailbox by Zimbra.

the class Appointment method chooseReplyTZ.

// Figure out the timezone to use for expressing start/end times for
// conflicting meetings.  Do our best to use a timezone familiar to the
// organizer.
private ICalTimeZone chooseReplyTZ(Invite invite) throws ServiceException {
    Account account = getMailbox().getAccount();
    Account organizer = invite.getOrganizerAccount();
    ICalTimeZone tz = invite.getStartTime().getTimeZone();
    if (tz == null && invite.isAllDayEvent()) {
        // floating time: use resource's timezone
        tz = Util.getAccountTimeZone(account);
        if (tz == null)
            ICalTimeZone.getUTC();
    } else {
        // tz != null || !allday
        if (tz == null || tz.sameAsUTC()) {
            if (organizer != null) {
                // For this case, let's assume the sender didn't really mean UTC.
                // This happens with Outlook and possibly more clients.
                tz = Util.getAccountTimeZone(organizer);
            } else {
                // If organizer is not a local user, use resource's timezone.
                tz = Util.getAccountTimeZone(account);
                if (tz == null)
                    ICalTimeZone.getUTC();
            }
        } else {
        // Timezone is not UTC.  We can safely assume the client sent the
        // correct local timezone.
        }
    }
    return tz;
}
Also used : Account(com.zimbra.cs.account.Account) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Aggregations

ICalTimeZone (com.zimbra.common.calendar.ICalTimeZone)62 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)17 ZComponent (com.zimbra.common.calendar.ZCalendar.ZComponent)14 TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)12 Element (com.zimbra.common.soap.Element)11 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)10 ServiceException (com.zimbra.common.service.ServiceException)10 ArrayList (java.util.ArrayList)10 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)8 Account (com.zimbra.cs.account.Account)8 Invite (com.zimbra.cs.mailbox.calendar.Invite)8 Mailbox (com.zimbra.cs.mailbox.Mailbox)7 IOException (java.io.IOException)7 Metadata (com.zimbra.cs.mailbox.Metadata)6 ItemId (com.zimbra.cs.service.util.ItemId)6 ParseException (java.text.ParseException)6 OperationContext (com.zimbra.cs.mailbox.OperationContext)5 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)5 ZimbraSoapContext (com.zimbra.soap.ZimbraSoapContext)5 GregorianCalendar (java.util.GregorianCalendar)5