Search in sources :

Example 6 with Alarm

use of com.zimbra.cs.mailbox.calendar.Alarm in project zm-mailbox by Zimbra.

the class CalendarUtils method parseInviteElementCommon.

/**
     * UID, DTSTAMP, and SEQUENCE **MUST** be set by caller
     *
     * @param account
     *            user receiving invite
     * @param element
     *            invite XML element
     * @param newInv
     *            Invite we are currently building up
     * @param oldTzMap
     *            time zone map from A DIFFERENT invite; if this method is
     *            called during modify operation, this map contains time zones
     *            before the modification; null if called during create
     *            operation
     * @return
     * @throws ServiceException
     */
private static void parseInviteElementCommon(Account account, MailItem.Type type, Element element, Invite newInv, boolean recurrenceIdAllowed, boolean recurAllowed) throws ServiceException {
    //zdsync
    String invId = element.getAttribute(MailConstants.A_ID, null);
    Element compElem = element.getOptionalElement(MailConstants.E_INVITE_COMPONENT);
    if (compElem != null) {
        element = compElem;
    }
    //zdsync
    String dts = element.getAttribute(MailConstants.A_CAL_DATETIME, null);
    TimeZoneMap tzMap = newInv.getTimeZoneMap();
    parseTimeZones(element.getParent(), tzMap);
    newInv.setItemType(type);
    // UID
    String uid = element.getAttribute(MailConstants.A_UID, null);
    if (uid != null && uid.length() > 0)
        newInv.setUid(uid);
    // RECURRENCE-ID
    if (recurrenceIdAllowed) {
        Element e = element.getOptionalElement(MailConstants.E_CAL_EXCEPTION_ID);
        if (e != null) {
            ParsedDateTime dt = parseDateTime(e, tzMap);
            RecurId recurId = new RecurId(dt, RecurId.RANGE_NONE);
            newInv.setRecurId(recurId);
        }
    } else {
        if (element.getOptionalElement(MailConstants.E_CAL_EXCEPTION_ID) != null) {
            throw ServiceException.INVALID_REQUEST("May not specify an <exceptId> in this request", null);
        }
    }
    String name = element.getAttribute(MailConstants.A_NAME, "");
    String location = element.getAttribute(MailConstants.A_CAL_LOCATION, "");
    // CATEGORIES
    for (Iterator<Element> catIter = element.elementIterator(MailConstants.E_CAL_CATEGORY); catIter.hasNext(); ) {
        String cat = catIter.next().getText();
        newInv.addCategory(cat);
    }
    // COMMENTs
    for (Iterator<Element> cmtIter = element.elementIterator(MailConstants.E_CAL_COMMENT); cmtIter.hasNext(); ) {
        String cmt = cmtIter.next().getText();
        newInv.addComment(cmt);
    }
    // CONTACTs
    for (Iterator<Element> cnIter = element.elementIterator(MailConstants.E_CAL_CONTACT); cnIter.hasNext(); ) {
        String contact = cnIter.next().getTextTrim();
        newInv.addContact(contact);
    }
    // GEO
    Element geoElem = element.getOptionalElement(MailConstants.E_CAL_GEO);
    if (geoElem != null) {
        Geo geo = Geo.parse(geoElem);
        newInv.setGeo(geo);
    }
    // URL
    String url = element.getAttribute(MailConstants.A_CAL_URL, null);
    newInv.setUrl(url);
    // SEQUENCE
    int seq = (int) element.getAttributeLong(MailConstants.A_CAL_SEQUENCE, 0);
    newInv.setSeqNo(seq);
    // SUMMARY (aka Name or Subject)
    newInv.setName(name);
    // DESCRIPTION
    Element descElem = element.getOptionalElement(MailConstants.E_CAL_DESCRIPTION);
    String desc = descElem != null ? descElem.getText() : null;
    Element descHtmlElem = element.getOptionalElement(MailConstants.E_CAL_DESC_HTML);
    String descHtml = descHtmlElem != null ? descHtmlElem.getText() : null;
    newInv.setDescription(desc, descHtml);
    boolean allDay = element.getAttributeBool(MailConstants.A_CAL_ALLDAY, false);
    newInv.setIsAllDayEvent(allDay);
    // DTSTART
    Element startElem;
    if (newInv.isTodo())
        startElem = element.getOptionalElement(MailConstants.E_CAL_START_TIME);
    else
        startElem = element.getElement(MailConstants.E_CAL_START_TIME);
    if (startElem != null) {
        ParsedDateTime dt = parseDtElement(startElem, tzMap, newInv);
        // fixup for bug 30121
        if (allDay && dt.hasTime()) {
            // If this is supposed to be an all-day event but DTSTART has time part, clear the time part.
            dt.setHasTime(false);
        } else if (!allDay && !dt.hasTime()) {
            // If the event isn't marked as all-day but DTSTART is date-only, the client simply forgot
            // to mark it all-day.  Do all-day implicitly.
            allDay = true;
            newInv.setIsAllDayEvent(allDay);
        }
        newInv.setDtStart(dt);
    }
    // DTEND (for VEVENT) or DUE (for VTODO)
    Element endElem = element.getOptionalElement(MailConstants.E_CAL_END_TIME);
    if (endElem != null) {
        ParsedDateTime dt = parseDtElement(endElem, tzMap, newInv);
        // fixup for bug 30121
        if (allDay && dt.hasTime()) {
            // If this is supposed to be an all-day event but DTEND has time part, clear the time part.
            dt.setHasTime(false);
        } else if (!allDay && !dt.hasTime()) {
            // If the event isn't marked as all-day but DTEND is date-only, the client simply forgot
            // to mark it all-day.  Do all-day implicitly.
            allDay = true;
            newInv.setIsAllDayEvent(allDay);
        }
        if (allDay && !newInv.isTodo()) {
            // HACK ALERT: okay, campers, here's the deal.
            // By definition, our end dates are EXCLUSIVE: DTEND is not
            // included.. eg a meeting 7-8pm actually stops at 7:59
            //
            // This makes sense for normal appointments, but apparently
            // this rule is confusing to people when making
            // all-day-events
            //
            // For all-day-events, people want to say that a 1-day-long
            // appointment starts on 11/1 and ends on 11/1, for example
            // this is inconsistent (and incompatible with RFC2445) but
            // it is what people want. Sooo, we to a bit of a hacky
            // translation when sending/receiving all-day-events.
            //
            dt = dt.add(ParsedDuration.ONE_DAY);
        }
        newInv.setDtEnd(dt);
    } else {
        // DURATION
        Element d = element.getOptionalElement(MailConstants.E_CAL_DURATION);
        if (d != null) {
            ParsedDuration pd = ParsedDuration.parse(d);
            newInv.setDuration(pd);
        }
    }
    // LOCATION
    newInv.setLocation(location);
    // STATUS
    String status = element.getAttribute(MailConstants.A_CAL_STATUS, newInv.isEvent() ? IcalXmlStrMap.STATUS_CONFIRMED : IcalXmlStrMap.STATUS_NEEDS_ACTION);
    validateAttr(IcalXmlStrMap.sStatusMap, MailConstants.A_CAL_STATUS, status);
    newInv.setStatus(status);
    // CLASS
    String classProp = element.getAttribute(MailConstants.A_CAL_CLASS, IcalXmlStrMap.CLASS_PUBLIC);
    validateAttr(IcalXmlStrMap.sClassMap, MailConstants.A_CAL_CLASS, classProp);
    newInv.setClassProp(classProp);
    // PRIORITY
    String priority = element.getAttribute(MailConstants.A_CAL_PRIORITY, null);
    newInv.setPriority(priority);
    if (newInv.isEvent()) {
        // FreeBusy
        String fb = element.getAttribute(MailConstants.A_APPT_FREEBUSY, null);
        if (fb != null) {
            newInv.setFreeBusy(fb);
            // Intended F/B takes precedence over TRANSP.
            if (IcalXmlStrMap.FBTYPE_FREE.equals(fb))
                newInv.setTransparency(IcalXmlStrMap.TRANSP_TRANSPARENT);
            else
                newInv.setTransparency(IcalXmlStrMap.TRANSP_OPAQUE);
        } else {
            // TRANSP is examined only when intended F/B is not supplied.
            String transp = element.getAttribute(MailConstants.A_APPT_TRANSPARENCY, IcalXmlStrMap.TRANSP_OPAQUE);
            validateAttr(IcalXmlStrMap.sTranspMap, MailConstants.A_APPT_TRANSPARENCY, transp);
            newInv.setTransparency(transp);
            // If opaque, don't set intended f/b because there are multiple possibilities.
            if (newInv.isTransparent())
                newInv.setFreeBusy(IcalXmlStrMap.FBTYPE_FREE);
        }
    }
    if (newInv.isTodo()) {
        // PERCENT-COMPLETE
        String pctComplete = element.getAttribute(MailConstants.A_TASK_PERCENT_COMPLETE, null);
        newInv.setPercentComplete(pctComplete);
        // COMPLETED
        String completed = element.getAttribute(MailConstants.A_TASK_COMPLETED, null);
        if (completed != null) {
            try {
                ParsedDateTime c = ParsedDateTime.parseUtcOnly(completed);
                newInv.setCompleted(c.getUtcTime());
            } catch (ParseException e) {
                throw ServiceException.INVALID_REQUEST("Invalid COMPLETED value: " + completed, e);
            }
        } else if (status.equals(IcalXmlStrMap.STATUS_COMPLETED)) {
            newInv.setCompleted(System.currentTimeMillis());
        } else {
            newInv.setCompleted(0);
        }
    }
    // ATTENDEEs
    boolean hasAttendees = false;
    for (Iterator<Element> iter = element.elementIterator(MailConstants.E_CAL_ATTENDEE); iter.hasNext(); ) {
        ZAttendee at = ZAttendee.parse(iter.next());
        newInv.addAttendee(at);
        hasAttendees = true;
    }
    if (hasAttendees && newInv.getMethod().equals(ICalTok.PUBLISH.toString())) {
        newInv.setMethod(ICalTok.REQUEST.toString());
    }
    // ORGANIZER
    Element orgElt = element.getOptionalElement(MailConstants.E_CAL_ORGANIZER);
    if (orgElt != null) {
        ZOrganizer org = ZOrganizer.parse(orgElt);
        newInv.setOrganizer(org);
    }
    // Once we have organizer and attendee information, we can tell if this account is the
    // organizer in this invite or not.
    newInv.setIsOrganizer(account);
    if (!newInv.isCancel()) {
        // draft flag
        // True means invite has changes that haven't been sent to attendees.
        boolean draft = element.getAttributeBool(MailConstants.A_CAL_DRAFT, false);
        newInv.setDraft(draft);
        // neverSent flag
        // True means attendees have never been notified for this invite.
        boolean neverSent = element.getAttributeBool(MailConstants.A_CAL_NEVER_SENT, false);
        newInv.setNeverSent(neverSent);
    }
    // RECUR
    Element recur = element.getOptionalElement(MailConstants.A_CAL_RECUR);
    if (recur != null) {
        if (!recurAllowed) {
            throw ServiceException.INVALID_REQUEST("No <recur> allowed in an exception", null);
        }
        // Ensure DTSTART is set if doing recurrence.
        ParsedDateTime st = newInv.getStartTime();
        if (st == null) {
            ParsedDateTime et = newInv.getEndTime();
            if (et != null) {
                if (et.hasTime())
                    st = et.add(ParsedDuration.NEGATIVE_ONE_SECOND);
                else
                    st = et.add(ParsedDuration.NEGATIVE_ONE_DAY);
                newInv.setDtStart(st);
            } else {
                // Both DTSTART and DTEND are unspecified.  Recurrence makes no sense!
                throw ServiceException.INVALID_REQUEST("recurrence used without DTSTART", null);
            }
        }
        Recurrence.IRecurrence recurrence = parseRecur(recur, tzMap, newInv.getStartTime(), newInv.getEndTime(), newInv.getDuration(), newInv.getRecurId());
        newInv.setRecurrence(recurrence);
    }
    // VALARMs
    Iterator<Element> alarmsIter = element.elementIterator(MailConstants.E_CAL_ALARM);
    while (alarmsIter.hasNext()) {
        Alarm alarm = Alarm.parse(alarmsIter.next());
        if (alarm != null)
            newInv.addAlarm(alarm);
    }
    List<ZProperty> xprops = parseXProps(element);
    for (ZProperty prop : xprops) newInv.addXProp(prop);
    newInv.validateDuration();
    //zdsync: must set this only after recur is processed
    if (invId != null) {
        try {
            int invIdInt = Integer.parseInt(invId);
            newInv.setInviteId(invIdInt);
        } catch (NumberFormatException e) {
        // ignore if invId is not a number, e.g. refers to a remote account
        }
    }
    if (dts != null) {
        newInv.setDtStamp(Long.parseLong(dts));
    }
    Element fragment = element.getOptionalElement(MailConstants.E_FRAG);
    if (fragment != null) {
        newInv.setFragment(fragment.getText());
    }
}
Also used : IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Recurrence(com.zimbra.cs.mailbox.calendar.Recurrence) ParsedDuration(com.zimbra.common.calendar.ParsedDuration) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Element(com.zimbra.common.soap.Element) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) Geo(com.zimbra.common.calendar.Geo) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Alarm(com.zimbra.cs.mailbox.calendar.Alarm) TimeZoneMap(com.zimbra.common.calendar.TimeZoneMap) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) ParseException(java.text.ParseException)

Example 7 with Alarm

use of com.zimbra.cs.mailbox.calendar.Alarm in project zm-mailbox by Zimbra.

the class ToXML method encodeInviteComponent.

public static Element encodeInviteComponent(Element parent, ItemIdFormatter ifmt, OperationContext octxt, CalendarItem calItem, /* may be null */
ItemId calId, /* may be null */
Invite invite, int fields, boolean neuter) throws ServiceException {
    boolean allFields = true;
    if (fields != NOTIFY_FIELDS) {
        allFields = false;
        if (!needToOutput(fields, Change.INVITE)) {
            return parent;
        }
    }
    Element e = parent.addElement(MailConstants.E_INVITE_COMPONENT);
    e.addAttribute(MailConstants.A_CAL_METHOD, invite.getMethod());
    e.addAttribute(MailConstants.A_CAL_COMPONENT_NUM, invite.getComponentNum());
    e.addAttribute(MailConstants.A_CAL_RSVP, invite.getRsvp());
    boolean allowPrivateAccess = calItem != null ? allowPrivateAccess(octxt, calItem) : true;
    if (allFields) {
        if (invite.isPublic() || allowPrivateAccess) {
            String priority = invite.getPriority();
            if (priority != null) {
                e.addAttribute(MailConstants.A_CAL_PRIORITY, priority);
            }
            e.addAttribute(MailConstants.A_NAME, invite.getName());
            e.addAttribute(MailConstants.A_CAL_LOCATION, invite.getLocation());
            List<String> categories = invite.getCategories();
            if (categories != null) {
                for (String cat : categories) {
                    e.addElement(MailConstants.E_CAL_CATEGORY).setText(cat);
                }
            }
            List<String> comments = invite.getComments();
            if (comments != null) {
                for (String cmt : comments) {
                    e.addElement(MailConstants.E_CAL_COMMENT).setText(cmt);
                }
            }
            List<String> contacts = invite.getContacts();
            if (contacts != null) {
                for (String contact : contacts) {
                    e.addElement(MailConstants.E_CAL_CONTACT).setText(contact);
                }
            }
            Geo geo = invite.getGeo();
            if (geo != null) {
                geo.toXml(e);
            }
            // Percent Complete (VTODO)
            if (invite.isTodo()) {
                String pct = invite.getPercentComplete();
                if (pct != null)
                    e.addAttribute(MailConstants.A_TASK_PERCENT_COMPLETE, pct);
                long completed = invite.getCompleted();
                if (completed != 0) {
                    ParsedDateTime c = ParsedDateTime.fromUTCTime(completed);
                    e.addAttribute(MailConstants.A_TASK_COMPLETED, c.getDateTimePartString());
                }
            }
            // Attendee(s)
            List<ZAttendee> attendees = invite.getAttendees();
            for (ZAttendee at : attendees) {
                at.toXml(e);
            }
            // Alarms
            Iterator<Alarm> alarmsIter = invite.alarmsIterator();
            while (alarmsIter.hasNext()) {
                Alarm alarm = alarmsIter.next();
                alarm.toXml(e);
            }
            // x-prop
            encodeXProps(e, invite.xpropsIterator());
            // fragment
            String fragment = invite.getFragment();
            if (!Strings.isNullOrEmpty(fragment)) {
                e.addAttribute(MailConstants.E_FRAG, fragment, Element.Disposition.CONTENT);
            }
            if (!invite.hasBlobPart()) {
                e.addAttribute(MailConstants.A_CAL_NO_BLOB, true);
            }
            // Description (plain and html)
            String desc = invite.getDescription();
            if (desc != null) {
                Element descElem = e.addElement(MailConstants.E_CAL_DESCRIPTION);
                descElem.setText(desc);
            }
            String descHtml = invite.getDescriptionHtml();
            BrowserDefang defanger = DefangFactory.getDefanger(MimeConstants.CT_TEXT_HTML);
            if (descHtml != null) {
                try {
                    descHtml = StringUtil.stripControlCharacters(descHtml);
                    descHtml = defanger.defang(descHtml, neuter);
                    Element descHtmlElem = e.addElement(MailConstants.E_CAL_DESC_HTML);
                    descHtmlElem.setText(descHtml);
                } catch (IOException ex) {
                    ZimbraLog.calendar.warn("Unable to defang HTML for SetAppointmentRequest", ex);
                }
            }
            if (invite.isEvent()) {
                if (calItem != null && calItem instanceof Appointment) {
                    Instance inst = Instance.fromInvite(calItem.getId(), invite);
                    Appointment appt = (Appointment) calItem;
                    e.addAttribute(MailConstants.A_APPT_FREEBUSY_ACTUAL, appt.getEffectiveFreeBusyActual(invite, inst));
                }
                e.addAttribute(MailConstants.A_APPT_FREEBUSY, invite.getFreeBusy());
                e.addAttribute(MailConstants.A_APPT_TRANSPARENCY, invite.getTransparency());
            }
            // Organizer
            if (invite.hasOrganizer()) {
                ZOrganizer org = invite.getOrganizer();
                org.toXml(e);
            }
            e.addAttribute(MailConstants.A_CAL_URL, invite.getUrl());
        }
        if (invite.isOrganizer()) {
            e.addAttribute(MailConstants.A_CAL_ISORG, true);
        }
        boolean isRecurring = false;
        e.addAttribute("x_uid", invite.getUid());
        e.addAttribute(MailConstants.A_UID, invite.getUid());
        e.addAttribute(MailConstants.A_CAL_SEQUENCE, invite.getSeqNo());
        //zdsync
        e.addAttribute(MailConstants.A_CAL_DATETIME, invite.getDTStamp());
        String itemId = null;
        if (calId != null) {
            itemId = calId.toString(ifmt);
        } else if (calItem != null) {
            itemId = ifmt.formatItemId(calItem);
        }
        if ((itemId != null) && !("0".equals(itemId))) {
            e.addAttribute(MailConstants.A_CAL_ID, /* calItemId */
            itemId);
            if (invite.isEvent()) {
                // for backward compat
                e.addAttribute(MailConstants.A_APPT_ID_DEPRECATE_ME, /* apptId */
                itemId);
            }
            if (calItem != null) {
                ItemId ciFolderId = new ItemId(calItem.getMailbox(), calItem.getFolderId());
                e.addAttribute(MailConstants.A_CAL_ITEM_FOLDER, /* ciFolder */
                ifmt.formatItemId(ciFolderId));
            }
        }
        Recurrence.IRecurrence recur = invite.getRecurrence();
        if (recur != null) {
            isRecurring = true;
            Element recurElt = e.addElement(MailConstants.E_CAL_RECUR);
            recur.toXml(recurElt);
        }
        e.addAttribute(MailConstants.A_CAL_STATUS, invite.getStatus());
        e.addAttribute(MailConstants.A_CAL_CLASS, invite.getClassProp());
        boolean allDay = invite.isAllDayEvent();
        boolean isException = invite.hasRecurId();
        if (isException) {
            e.addAttribute(MailConstants.A_CAL_IS_EXCEPTION, true);
            RecurId rid = invite.getRecurId();
            e.addAttribute(MailConstants.A_CAL_RECURRENCE_ID_Z, rid.getDtZ());
            encodeRecurId(e, rid, allDay);
        }
        boolean forceUTC = DebugConfig.calendarForceUTC && !isRecurring && !isException && !allDay;
        ParsedDateTime dtStart = invite.getStartTime();
        if (dtStart != null) {
            encodeDtStart(e, dtStart, allDay, forceUTC);
        }
        ParsedDateTime dtEnd = invite.getEndTime();
        if (dtEnd != null) {
            encodeDtEnd(e, dtEnd, allDay, invite.isTodo(), forceUTC);
        }
        ParsedDuration dur = invite.getDuration();
        if (dur != null) {
            dur.toXml(e);
        }
        if (allDay) {
            e.addAttribute(MailConstants.A_CAL_ALLDAY, true);
        }
        if (invite.isDraft()) {
            e.addAttribute(MailConstants.A_CAL_DRAFT, true);
        }
        if (invite.isNeverSent()) {
            e.addAttribute(MailConstants.A_CAL_NEVER_SENT, true);
        }
    }
    return e;
}
Also used : Appointment(com.zimbra.cs.mailbox.Appointment) Recurrence(com.zimbra.cs.mailbox.calendar.Recurrence) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Instance(com.zimbra.cs.mailbox.CalendarItem.Instance) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) ParsedDuration(com.zimbra.common.calendar.ParsedDuration) Element(com.zimbra.common.soap.Element) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) BrowserDefang(com.zimbra.cs.html.BrowserDefang) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) IOException(java.io.IOException) ItemId(com.zimbra.cs.service.util.ItemId) Geo(com.zimbra.common.calendar.Geo) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) Alarm(com.zimbra.cs.mailbox.calendar.Alarm) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime)

Example 8 with Alarm

use of com.zimbra.cs.mailbox.calendar.Alarm in project zm-mailbox by Zimbra.

the class ToXML method alarmDataToJaxb.

public static AlarmDataInfo alarmDataToJaxb(CalendarItem calItem, AlarmData alarmData) {
    AlarmDataInfo alarm = new AlarmDataInfo();
    long nextAlarm = getNextAlarmTime(calItem);
    if (nextAlarm < Long.MAX_VALUE) {
        alarm.setNextAlarm(nextAlarm);
    }
    long alarmInstStart = alarmData.getNextInstanceStart();
    if (alarmInstStart != 0) {
        alarm.setAlarmInstanceStart(alarmInstStart);
    }
    int alarmInvId = alarmData.getInvId();
    int alarmCompNum = alarmData.getCompNum();
    Invite alarmInv = calItem.getInvite(alarmInvId, alarmCompNum);
    if (alarmInv != null) {
        // Some info on the meeting instance the reminder is for.
        // These allow the UI to display tooltip and issue a Get
        // call on the correct meeting instance.
        alarm.setName(alarmInv.getName());
        alarm.setLocation(alarmInv.getLocation());
        alarm.setInvId(alarmInvId);
        alarm.setComponentNum(alarmCompNum);
    }
    Alarm alarmObj = alarmData.getAlarm();
    if (alarmObj != null) {
        alarm.setAlarm(alarmObj.toJaxb());
    }
    return alarm;
}
Also used : AlarmDataInfo(com.zimbra.soap.mail.type.AlarmDataInfo) Alarm(com.zimbra.cs.mailbox.calendar.Alarm) Mountpoint(com.zimbra.cs.mailbox.Mountpoint) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 9 with Alarm

use of com.zimbra.cs.mailbox.calendar.Alarm in project zm-mailbox by Zimbra.

the class CalItemReminderService method scheduleNextReminders.

/**
     * Schedules next reminders for the calendar item.
     *
     * @param calItem
     * @param email
     * @param sms
     */
static void scheduleNextReminders(CalendarItem calItem, boolean email, boolean sms) {
    try {
        CalendarItem.AlarmData alarmData = calItem.getNextEmailAlarm();
        if (alarmData == null)
            return;
        boolean emailAlarmExists = true;
        boolean smsAlarmExists = false;
        Alarm emailAlarm = alarmData.getAlarm();
        List<ZAttendee> recipients = emailAlarm.getAttendees();
        if (recipients != null && !recipients.isEmpty()) {
            emailAlarmExists = false;
            Account acct = calItem.getAccount();
            String defaultEmailAddress = acct.getPrefCalendarReminderEmail();
            String defaultDeviceAddress = acct.getCalendarReminderDeviceEmail();
            for (ZAttendee recipient : recipients) {
                if (recipient.getAddress().equals(defaultEmailAddress)) {
                    emailAlarmExists = true;
                }
                if (recipient.getAddress().equals(defaultDeviceAddress)) {
                    smsAlarmExists = true;
                }
            }
        }
        if (emailAlarmExists && email) {
            scheduleReminder(new CalItemEmailReminderTask(), calItem, alarmData);
        }
        if (smsAlarmExists && sms) {
            scheduleReminder(new CalItemSmsReminderTask(), calItem, alarmData);
        }
    } catch (ServiceException e) {
        ZimbraLog.scheduler.error("Error in scheduling reminder task", e);
    }
}
Also used : CalendarItem(com.zimbra.cs.mailbox.CalendarItem) Account(com.zimbra.cs.account.Account) ServiceException(com.zimbra.common.service.ServiceException) Alarm(com.zimbra.cs.mailbox.calendar.Alarm) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee)

Example 10 with Alarm

use of com.zimbra.cs.mailbox.calendar.Alarm in project zm-mailbox by Zimbra.

the class CalendarItem method processNewInviteRequestOrCancel.

/**
     *
     * @param pm
     * @param newInvite
     * @param folderId
     * @param nextAlarm
     * @param preserveAlarms
     * @param discardExistingInvites
     * @param batch - if true this call will not update the recurrence and may not persist to the data.
     *                The caller needs to persist the data by calling setContent().
     * @return
     * @throws ServiceException
     */
private boolean processNewInviteRequestOrCancel(ParsedMessage pm, Invite newInvite, int folderId, long nextAlarm, boolean preserveAlarms, boolean discardExistingInvites, boolean batch) throws ServiceException {
    // trace logging
    if (!newInvite.hasRecurId())
        ZimbraLog.calendar.info("Modifying CalendarItem: id=%d, folderId=%d, method=%s, subject=\"%s\", UID=%s", mId, getFolderId(), newInvite.getMethod(), newInvite.isPublic() ? newInvite.getName() : "(private)", mUid);
    else
        ZimbraLog.calendar.info("Modifying CalendarItem: id=%d, folderId=%d, method=%s, subject=\"%s\", UID=%s, recurId=%s", mId, getFolderId(), newInvite.getMethod(), newInvite.isPublic() ? newInvite.getName() : "(private)", mUid, newInvite.getRecurId().getDtZ());
    newInvite.sanitize(false);
    OperationContext octxt = getMailbox().getOperationContext();
    Account authAccount = octxt != null ? octxt.getAuthenticatedUser() : null;
    boolean asAdmin = octxt != null ? octxt.isUsingAdminPrivileges() : false;
    boolean isCancel = newInvite.isCancel();
    boolean requirePrivateCheck = requirePrivateCheck(newInvite);
    short rightsNeeded = isCancel ? (short) (ACL.RIGHT_DELETE | ACL.RIGHT_WRITE) : ACL.RIGHT_WRITE;
    if (!canAccess(rightsNeeded, authAccount, asAdmin, requirePrivateCheck))
        throw ServiceException.PERM_DENIED("you do not have sufficient permissions on this calendar item");
    // Don't allow moving a private appointment on behalf of another user,
    // unless that other user is a calendar resource.
    boolean isCalendarResource = getMailbox().getAccount() instanceof CalendarResource;
    boolean denyPrivateAccess = requirePrivateCheck ? !allowPrivateAccess(authAccount, asAdmin) : false;
    if (!newInvite.isPublic() || !isPublic()) {
        if (folderId != getFolderId()) {
            Folder folder = getMailbox().getFolderById(folderId);
            if (!allowPrivateAccess(folder, authAccount, asAdmin)) {
                denyPrivateAccess = true;
                if (!isCalendarResource)
                    throw ServiceException.PERM_DENIED("you do not have permission to update/cancel private calendar item in target folder");
            }
        }
    }
    // Do not allow organizer to be changed. (bug 74400)
    boolean organizerChanged = organizerChangeCheck(newInvite, true);
    ZOrganizer newOrganizer = newInvite.getOrganizer();
    // of the invite.
    if (isCancel) {
        boolean cancelAll;
        boolean outdated;
        if (!newInvite.hasRecurId()) {
            cancelAll = true;
            // Canceling series.  Check the sequencing requirement to make sure the invite isn't outdated.
            Invite series = getInvite((RecurId) null);
            // If series invite is not found, assume cancel is not outdated.
            outdated = series != null && !newInvite.isSameOrNewerVersion(series);
        } else {
            // Canceling an instance.  It's a total cancel only if mInvites has one invite and it matches
            // the recurrence id.  (subject to sequencing requirements)
            cancelAll = false;
            outdated = false;
            Invite curr = getInvite(newInvite.getRecurId());
            if (curr != null) {
                if (newInvite.isSameOrNewerVersion(curr)) {
                    cancelAll = true;
                    // See if there any non-cancel invites besides the one being canceled.
                    for (Invite inv : mInvites) {
                        if (!inv.equals(curr) && !inv.isCancel()) {
                            cancelAll = false;
                            break;
                        }
                    }
                } else {
                    // There is already a newer invite.  Ignore the cancel.
                    outdated = true;
                }
            }
        }
        if (outdated) {
            ZimbraLog.calendar.info("Ignoring outdated cancel request");
            return false;
        }
        if (cancelAll) {
            Folder trash = mMailbox.getFolderById(Mailbox.ID_FOLDER_TRASH);
            move(trash);
            // If we have revisions enabled we need to force metadata write to db because version field changed.
            if (getMaxRevisions() != 1)
                saveMetadata();
            return true;
        }
    }
    // Clear all replies if replacing appointment in trash folder with a new invite.  All existing invites are
    // being discarded, and so all existing replies must be discarded as well.
    Folder folder = getMailbox().getFolderById(folderId);
    if (!isCancel && discardExistingInvites && inTrash() && !folder.inTrash()) {
        mReplyList.mReplies.clear();
    }
    // Handle change to the series that involves time and/or recurrence.  In Exchange compatibility mode,
    // time/recurrence change blows away all exception instances.  In non-compat mode (old ZCS behavior),
    // look for change in the start time and shift the time part of exceptions' RECURRENCE-ID by the same delta.
    boolean needRecurrenceIdUpdate = false;
    ParsedDateTime oldDtStart = null;
    ParsedDuration dtStartMovedBy = null;
    ArrayList<Invite> toUpdate = new ArrayList<Invite>();
    if (!discardExistingInvites && !isCancel && newInvite.isRecurrence()) {
        Invite defInv = getDefaultInviteOrNull();
        if (defInv != null && defInv.isRecurrence()) {
            if (!getAccount().isCalendarKeepExceptionsOnSeriesTimeChange()) {
                // Exchange compatibility mode
                InviteChanges ic = new InviteChanges(defInv, newInvite);
                if (ic.isExceptionRemovingChange()) {
                    discardExistingInvites = true;
                }
            } else {
                // old ZCS behavior
                // Be careful.  If invites got delivered out of order, we may have defInv that's not
                // a series.  Imagine 1st invite received was an exception and 2nd was the series.
                // In that situation we simply skip the DTSTART shift calculation.
                oldDtStart = defInv.getStartTime();
                ParsedDateTime newDtStart = newInvite.getStartTime();
                //if (newDtStart != null && oldDtStart != null && !newDtStart.sameTime(oldDtStart))
                if (newDtStart != null && oldDtStart != null && !newDtStart.equals(oldDtStart)) {
                    // Find the series frequency.
                    Frequency freq = null;
                    IRecurrence recurrence = newInvite.getRecurrence();
                    if (recurrence != null) {
                        Iterator rulesIter = recurrence.addRulesIterator();
                        if (rulesIter != null) {
                            for (; rulesIter.hasNext(); ) {
                                Object ruleObj = rulesIter.next();
                                if (ruleObj instanceof SimpleRepeatingRule) {
                                    SimpleRepeatingRule series = (SimpleRepeatingRule) ruleObj;
                                    ZRecur recur = series.getRule();
                                    freq = recur.getFrequency();
                                    break;
                                }
                            }
                        }
                    }
                    // Maximum allowed delta depends on the frequency.
                    ParsedDuration deltaLimit = null;
                    if (freq != null) {
                        switch(freq) {
                            case DAILY:
                                deltaLimit = ParsedDuration.ONE_DAY;
                                break;
                            case WEEKLY:
                            case MONTHLY:
                            case YEARLY:
                                // Do the RECURRENCE-ID adjustment only when DTSTART moved by 7 days or less.
                                // If it moved by more, it gets too complicated to figure out what the old RECURRENCE-ID
                                // should be in the new series.  Just blow away all exceptions.
                                deltaLimit = ParsedDuration.ONE_WEEK;
                                break;
                            default:
                                // Secondly/minutely/hourly rules are too frequent to allow recurrence id shifting.
                                break;
                        }
                    }
                    if (deltaLimit != null) {
                        ParsedDuration delta = newDtStart.difference(oldDtStart);
                        if (delta.abs().compareTo(deltaLimit) < 0) {
                            needRecurrenceIdUpdate = true;
                            dtStartMovedBy = delta;
                        }
                    }
                }
            }
        }
    }
    // found, inherit from the series invite.
    if (!discardExistingInvites && preserveAlarms) {
        Invite localSeries = null;
        Invite alarmSourceInv = null;
        for (Invite inv : mInvites) {
            if (recurrenceIdsMatch(inv, newInvite)) {
                alarmSourceInv = inv;
                break;
            }
            if (!inv.hasRecurId())
                localSeries = inv;
        }
        if (alarmSourceInv == null)
            alarmSourceInv = localSeries;
        if (alarmSourceInv != null) {
            newInvite.clearAlarms();
            for (Iterator<Alarm> alarmIter = alarmSourceInv.alarmsIterator(); alarmIter.hasNext(); ) {
                newInvite.addAlarm(alarmIter.next());
            }
        }
    }
    // Is this a series update invite from ZCO?  If so, we have to treat all exceptions as local-only
    // and make them snap to series.
    boolean zcoSeriesUpdate = false;
    ZProperty xzDiscardExcepts = newInvite.getXProperty(ICalTok.X_ZIMBRA_DISCARD_EXCEPTIONS.toString());
    if (xzDiscardExcepts != null)
        zcoSeriesUpdate = xzDiscardExcepts.getBoolValue();
    // Is this an update to the series with UNTIL in the rule?  If so, we need to remove exceptions
    // whose RECURRENCE-ID come later than UNTIL. (bug 11870)
    long seriesUntil = Long.MAX_VALUE;
    if (!isCancel && !newInvite.hasRecurId()) {
        ParsedDateTime dtStart = newInvite.getStartTime();
        IRecurrence recur = newInvite.getRecurrence();
        if (recur != null && dtStart != null) {
            ICalTimeZone tz = dtStart.getTimeZone();
            // Find the repeating rule.
            Iterator<?> iter = recur.addRulesIterator();
            if (iter != null) {
                for (; iter.hasNext(); ) {
                    IRecurrence cur = (IRecurrence) iter.next();
                    if (cur.getType() == Recurrence.TYPE_REPEATING) {
                        ZRecur rrule = ((Recurrence.SimpleRepeatingRule) cur).getRule();
                        ParsedDateTime until = rrule.getUntil();
                        if (until != null)
                            seriesUntil = Math.min(until.getDateForRecurUntil(tz).getTime(), seriesUntil);
                    }
                }
            }
        }
    }
    // Check if exception instances are made obsolete by updated recurrence rule.  (bug 47061)
    Set<String> obsoletedRecurIdZs = new HashSet<String>();
    if (!isCancel && newInvite.isRecurrence()) {
        Invite seriesInv = null;
        // Find the range of existing exception instances.
        long rangeStart = Long.MAX_VALUE;
        long rangeEnd = Long.MIN_VALUE;
        for (Invite inv : mInvites) {
            if (inv.hasRecurId()) {
                RecurId rid = inv.getRecurId();
                ParsedDateTime ridDt = rid.getDt();
                if (ridDt != null) {
                    // Turn Outlook-style all-day RecurId to standard-style.
                    if (inv.isAllDayEvent() && ridDt.hasTime() && ridDt.hasZeroTime()) {
                        ParsedDateTime ridDtFixed = (ParsedDateTime) ridDt.clone();
                        ridDtFixed.setHasTime(false);
                        rid = new RecurId(ridDtFixed, rid.getRange());
                        ridDt = rid.getDt();
                    }
                    // Adjust start time if necessary.
                    RecurId adjustedRid;
                    long adjustedT;
                    if (dtStartMovedBy != null) {
                        ParsedDateTime dt = ridDt.add(dtStartMovedBy);
                        adjustedRid = new RecurId(dt, rid.getRange());
                        adjustedT = dt.getUtcTime();
                    } else {
                        adjustedRid = rid;
                        adjustedT = ridDt.getUtcTime();
                    }
                    rangeStart = Math.min(rangeStart, adjustedT);
                    rangeEnd = Math.max(rangeEnd, adjustedT);
                    // initially all instances considered obsolete
                    obsoletedRecurIdZs.add(adjustedRid.getDtZ());
                }
            } else {
                seriesInv = inv;
            }
        }
        // Extend the range by a day on both ends to compensate for all-day appointments.
        // 25 hours to accommodate DST onset dates
        long millisIn25Hours = 25 * 60 * 60 * 1000;
        if (rangeStart != Long.MAX_VALUE)
            rangeStart -= millisIn25Hours;
        if (rangeEnd != Long.MIN_VALUE)
            rangeEnd += millisIn25Hours;
        if (rangeStart != Long.MAX_VALUE && rangeEnd != Long.MIN_VALUE && rangeStart <= rangeEnd) {
            // so the final instance is included in the range
            ++rangeEnd;
            IRecurrence recur = newInvite.getRecurrence();
            if (recur instanceof RecurrenceRule) {
                RecurrenceRule rrule = (RecurrenceRule) recur;
                List<Instance> instances = rrule.expandInstances(getId(), rangeStart, rangeEnd);
                if (instances != null) {
                    for (Instance inst : instances) {
                        Invite refInv = seriesInv != null ? seriesInv : newInvite;
                        RecurId rid = inst.makeRecurId(refInv);
                        // Turn Outlook-style all-day RecurId to standard-style.
                        if (refInv.isAllDayEvent() && rid.getDt() != null) {
                            ParsedDateTime ridDtFixed = (ParsedDateTime) rid.getDt().clone();
                            ridDtFixed.setHasTime(false);
                            rid = new RecurId(ridDtFixed, rid.getRange());
                        }
                        // "Un-obsolete" the surviving recurrence ids.
                        obsoletedRecurIdZs.remove(rid.getDtZ());
                    }
                }
            } else if (recur != null) {
                // This shouldn't happen.
                ZimbraLog.calendar.warn("Expected RecurrenceRule object, but got " + recur.getClass().getName());
            }
        }
    }
    boolean addNewOne = true;
    boolean replaceExceptionBodyWithSeriesBody = false;
    boolean modifiedCalItem = false;
    // the invite which has been made obsolete by the new one coming in
    Invite prev = null;
    // Invites to remove from our blob store
    ArrayList<Invite> toRemove = new ArrayList<Invite>();
    // indexes to remove from mInvites
    ArrayList<Integer> idxsToRemove = new ArrayList<Integer>();
    // get current size because we may add to the list in the loop
    int numInvitesCurrent = mInvites.size();
    for (int i = 0; i < numInvitesCurrent; i++) {
        Invite cur = mInvites.get(i);
        // If request is a cancellation of entire appointment, simply add each invite to removal list.
        if (isCancel && !newInvite.hasRecurId()) {
            addNewOne = false;
            modifiedCalItem = true;
            toRemove.add(cur);
            idxsToRemove.add(0, i);
            continue;
        }
        // Use DTSTART for comparison rather than RECURRENCE-ID.
        if (!isCancel && cur.hasRecurId()) {
            ParsedDateTime instDtStart = cur.getStartTime();
            if (instDtStart != null && instDtStart.getUtcTime() > seriesUntil) {
                modifiedCalItem = true;
                toRemove.add(cur);
                idxsToRemove.add(0, i);
                continue;
            }
        }
        // Remove exceptions obsoleted by changed RRULE.  (bug 47061)
        if (cur.hasRecurId() && !obsoletedRecurIdZs.isEmpty()) {
            RecurId rid = cur.getRecurId();
            if (rid != null && rid.getDt() != null) {
                // Turn Outlook-style all-day RecurId to standard-style.
                ParsedDateTime ridDt = rid.getDt();
                if (cur.isAllDayEvent() && ridDt.hasTime() && ridDt.hasZeroTime()) {
                    ParsedDateTime ridDtFixed = (ParsedDateTime) ridDt.clone();
                    ridDtFixed.setHasTime(false);
                    rid = new RecurId(ridDtFixed, rid.getRange());
                }
                // Adjust start time if necessary.
                RecurId adjustedRid;
                if (dtStartMovedBy != null) {
                    ParsedDateTime dt = rid.getDt().add(dtStartMovedBy);
                    adjustedRid = new RecurId(dt, rid.getRange());
                } else {
                    adjustedRid = rid;
                }
                if (obsoletedRecurIdZs.contains(adjustedRid.getDtZ())) {
                    modifiedCalItem = true;
                    toRemove.add(cur);
                    idxsToRemove.add(0, i);
                    continue;
                }
            }
        }
        boolean matchingRecurId = recurrenceIdsMatch(cur, newInvite);
        if (discardExistingInvites || matchingRecurId) {
            if (discardExistingInvites || newInvite.isSameOrNewerVersion(cur)) {
                // Invite is local-only only if both old and new are local-only.
                newInvite.setLocalOnly(cur.isLocalOnly() && newInvite.isLocalOnly());
                toRemove.add(cur);
                // add to FRONT of list, so when we iterate for the removals we go from HIGHER TO LOWER
                // that way the numbers all match up as the list contracts!
                idxsToRemove.add(0, Integer.valueOf(i));
                boolean invalidateReplies = false;
                if (!discardExistingInvites) {
                    InviteChanges invChg = new InviteChanges(cur, newInvite);
                    invalidateReplies = invChg.isReplyInvalidatingChange();
                }
                if (discardExistingInvites || invalidateReplies) {
                    // clean up any old REPLYs that have been made obsolete by this new invite
                    mReplyList.removeObsoleteEntries(newInvite.getRecurId(), newInvite.getSeqNo(), newInvite.getDTStamp());
                } else {
                    // If the change is minor, don't discard earlier replies.  Organizer may have incremented the
                    // sequence unnecessarily, and we have to cope with this by bumping up the sequence in the
                    // replies accordingly.
                    mReplyList.upgradeEntriesToNewSeq(newInvite.getRecurId(), newInvite.getSeqNo(), newInvite.getDTStamp());
                }
                prev = cur;
                modifiedCalItem = true;
                if (isCancel && !newInvite.hasRecurId()) {
                    // can't CANCEL just the recurId=null entry -- we must delete the whole appointment
                    addNewOne = false;
                }
            } else {
                // perhaps delivered out of order.  Ignore it.
                return false;
            }
        } else if (!isCancel) {
            modifiedCalItem = true;
            boolean addToUpdateList = false;
            if (organizerChanged) {
                // If organizer is changing on any invite, change it on all invites.
                cur.setOrganizer(newOrganizer);
                addToUpdateList = true;
            }
            if (needRecurrenceIdUpdate) {
                // Adjust RECURRENCE-ID by the delta in series DTSTART, if recurrence id value has the
                // same time of day as old DTSTART.
                RecurId rid = cur.getRecurId();
                if (rid != null && rid.getDt() != null && oldDtStart != null) {
                    ParsedDateTime ridDt = rid.getDt();
                    if (ridDt.sameTime(oldDtStart)) {
                        ParsedDateTime dt = rid.getDt().add(dtStartMovedBy);
                        RecurId newRid = new RecurId(dt, rid.getRange());
                        cur.setRecurId(newRid);
                        // used in RECURRENCE-ID and adjust DTEND accordingly.
                        if (cur.isCancel()) {
                            cur.setDtStart(dt);
                            ParsedDateTime dtEnd = cur.getEndTime();
                            if (dtEnd != null) {
                                ParsedDateTime dtEndMoved = dtEnd.add(dtStartMovedBy);
                                cur.setDtEnd(dtEndMoved);
                            }
                        }
                        addToUpdateList = true;
                    }
                }
            }
            // organizer) are left alone.
            if (!newInvite.hasRecurId() && cur.hasRecurId() && (zcoSeriesUpdate || cur.isLocalOnly())) {
                if (cur.isCancel()) {
                    // Local-only cancellations are undone by update to the series.
                    toRemove.add(cur);
                    // add to FRONT of list, so when we iterate for the removals we go from HIGHER TO LOWER
                    // that way the numbers all match up as the list contracts!
                    idxsToRemove.add(0, Integer.valueOf(i));
                    // clean up any old REPLYs that have been made obsolete by this new invite
                    mReplyList.removeObsoleteEntries(newInvite.getRecurId(), newInvite.getSeqNo(), newInvite.getDTStamp());
                    addToUpdateList = false;
                } else {
                    replaceExceptionBodyWithSeriesBody = true;
                    // Recreate invite with data from newInvite, but preserve alarm info.
                    Invite copy = newInvite.newCopy();
                    // It's still local-only.
                    copy.setLocalOnly(true);
                    copy.setMailItemId(cur.getMailItemId());
                    copy.setComponentNum(cur.getComponentNum());
                    copy.setSeqNo(cur.getSeqNo());
                    copy.setDtStamp(cur.getDTStamp());
                    copy.setRecurId(cur.getRecurId());
                    // because we're only dealing with exceptions
                    copy.setRecurrence(null);
                    ParsedDateTime start = cur.getRecurId().getDt();
                    if (start != null) {
                        // snap back to series start time
                        copy.setDtStart(start);
                        ParsedDuration dur = cur.getDuration();
                        if (dur != null) {
                            copy.setDtEnd(null);
                            copy.setDuration(dur);
                        } else {
                            copy.setDuration(null);
                            dur = cur.getEffectiveDuration();
                            ParsedDateTime end = null;
                            if (dur != null)
                                end = start.add(dur);
                            copy.setDtEnd(end);
                        }
                    } else {
                        copy.setDtStart(null);
                        copy.setDtEnd(cur.getEndTime());
                        copy.setDuration(null);
                    }
                    copy.clearAlarms();
                    for (Iterator<Alarm> iter = cur.alarmsIterator(); iter.hasNext(); ) {
                        copy.addAlarm(iter.next());
                    }
                    // Series was updated, so change this exception's partstat to NEEDS-ACTION.
                    ZAttendee me = copy.getMatchingAttendee(getAccount());
                    if (me != null)
                        me.setPartStat(IcalXmlStrMap.PARTSTAT_NEEDS_ACTION);
                    mInvites.set(i, copy);
                    addToUpdateList = true;
                }
            }
            if (addToUpdateList)
                toUpdate.add(cur);
        }
    }
    boolean callProcessPartStat = false;
    if (addNewOne) {
        newInvite.setCalendarItem(this);
        // unless that other user is a calendar resource.
        if (denyPrivateAccess && prev != null && !prev.isPublic() && !isCalendarResource)
            throw ServiceException.PERM_DENIED("you do not have sufficient permissions on this calendar item");
        if (prev != null && !newInvite.isOrganizer() && newInvite.sentByMe()) {
            // A non-organizer attendee is modifying data on his/her
            // appointment/task.  Any information that is tracked in
            // metadata rather than in the iCal MIME part must be
            // carried over from the last invite to the new one.
            newInvite.setPartStat(prev.getPartStat());
            newInvite.setRsvp(prev.getRsvp());
            newInvite.getCalendarItem().saveMetadata();
        // No need to mark invite as modified item in mailbox as
        // it has already been marked as a created item.
        } else {
            callProcessPartStat = true;
        }
        newInvite.setClassPropSetByMe(newInvite.sentByMe());
        // retain the value and therefore don't allow the organizer to override it.
        if (prev != null && !newInvite.isOrganizer() && !newInvite.sentByMe()) {
            if (!prev.isPublic() && prev.classPropSetByMe()) {
                newInvite.setClassProp(prev.getClassProp());
                newInvite.setClassPropSetByMe(true);
            }
        }
        mInvites.add(newInvite);
        // the appointment/task stores an uber-tzmap, for its uber-recurrence
        // this might give us problems if we had two invites with conflicting TZ
        // defs....should be very unlikely
        mTzMap.add(newInvite.getTimeZoneMap());
        // TIM: don't write the blob until the end of the function (so we only do one write for the update)
        //            modifyBlob(toRemove, replaceExistingInvites, toUpdate, pm, newInvite, locator, isCancel, !denyPrivateAccess);
        modifiedCalItem = true;
    } else {
    // TIM: don't write the blob until the end of the function (so we only do one write for the update)
    //            modifyBlob(toRemove, replaceExistingInvites, toUpdate, null, null, locator, isCancel, !denyPrivateAccess);
    }
    // now remove the inviteid's from our list
    for (Iterator<Integer> iter = idxsToRemove.iterator(); iter.hasNext(); ) {
        assert (modifiedCalItem);
        Integer i = iter.next();
        mInvites.remove(i.intValue());
    }
    // Check if there are any surviving non-cancel invites after applying the update.
    // Also check for changes in flags.
    int oldFlags = mData.getFlags();
    int newFlags = mData.getFlags() & ~(Flag.BITMASK_ATTACHED | Flag.BITMASK_DRAFT | Flag.BITMASK_HIGH_PRIORITY | Flag.BITMASK_LOW_PRIORITY);
    boolean hasSurvivingRequests = false;
    for (Invite cur : mInvites) {
        String method = cur.getMethod();
        if (method.equals(ICalTok.REQUEST.toString()) || method.equals(ICalTok.PUBLISH.toString())) {
            hasSurvivingRequests = true;
            if (cur.hasAttachment())
                newFlags |= Flag.BITMASK_ATTACHED;
            if (cur.isDraft())
                newFlags |= Flag.BITMASK_DRAFT;
            if (cur.isHighPriority())
                newFlags |= Flag.BITMASK_HIGH_PRIORITY;
            if (cur.isLowPriority())
                newFlags |= Flag.BITMASK_LOW_PRIORITY;
        }
    }
    if (newFlags != oldFlags) {
        mData.setFlags(newFlags);
        modifiedCalItem = true;
    }
    if (!hasSurvivingRequests) {
        if (!isCancel)
            ZimbraLog.calendar.warn("Invalid state: deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " while processing a non-cancel request");
        else
            ZimbraLog.calendar.warn("Invalid state: deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " because it has no invite after applying cancel invite");
        // delete this appointment/task from the table,
        delete();
        // it doesn't have anymore REQUESTs!
        return false;
    } else {
        if (nextAlarm > 0 && mAlarmData != null && mAlarmData.getNextAtBase() != nextAlarm)
            modifiedCalItem = true;
        if (modifiedCalItem) {
            if (!batch && !updateRecurrence(nextAlarm)) {
                // no default invite!  This appointment/task no longer valid
                ZimbraLog.calendar.warn("Invalid state: deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " because it has no invite");
                delete();
                return false;
            } else {
                if (callProcessPartStat) {
                    // processPartStat() must be called after
                    // updateRecurrence() has been called.  (bug 8072)
                    processPartStat(newInvite, pm != null ? pm.getMimeMessage() : null, false, newInvite.getPartStat());
                }
                if (getFolderId() != folderId) {
                    // Move appointment/task to a different folder.
                    move(folder);
                }
                // Did the appointment have a blob before the change?
                boolean hadBlobPart = false;
                Invite[] oldInvs = getInvites();
                if (oldInvs != null) {
                    for (Invite oldInv : oldInvs) {
                        if (oldInv.hasBlobPart()) {
                            hadBlobPart = true;
                            break;
                        }
                    }
                }
                // Update blob if adding a new ParsedMessage or if there is already a blob, in which
                // case we may have to delete a section from it.
                boolean newInvHasBlobPart = newInvite.hasBlobPart();
                if (hadBlobPart || newInvHasBlobPart) {
                    if (addNewOne) {
                        modifyBlob(toRemove, discardExistingInvites, toUpdate, pm, newInvite, isCancel, !denyPrivateAccess, true, replaceExceptionBodyWithSeriesBody);
                    } else {
                        if (!newInvHasBlobPart)
                            // force existing MIME part to be removed
                            toRemove.add(newInvite);
                        modifyBlob(toRemove, discardExistingInvites, toUpdate, null, null, isCancel, !denyPrivateAccess, true, replaceExceptionBodyWithSeriesBody);
                    }
                // TIM: modifyBlob will save the metadata for us as a side-effect
                //                      saveMetadata();
                } else {
                    markItemModified(Change.INVITE);
                    try {
                        if (batch) {
                            persistBatchedChanges = true;
                        } else {
                            // call setContent here so that MOD_CONTENT is updated...this is required
                            // for the index entry to be correctly updated (bug 39463)
                            setContent(null, null);
                        }
                    } catch (IOException e) {
                        throw ServiceException.FAILURE("IOException", e);
                    }
                }
                // remove the item if all the instances are canceled.
                Invite defInvite = getDefaultInviteOrNull();
                if (defInvite != null) {
                    Collection<Instance> instances = expandInstances(0, Long.MAX_VALUE, false);
                    if (instances.isEmpty()) {
                        ZimbraLog.calendar.warn("Deleting calendar item " + getId() + " in mailbox " + getMailboxId() + " because it has no invite after applying request/cancel invite");
                        delete();
                        return true;
                    }
                }
                Callback cb = getCallback();
                if (cb != null)
                    cb.modified(this);
                return true;
            }
        } else {
            if (getFolderId() != folderId) {
                // Move appointment/task to a different folder.
                move(folder);
            }
            return false;
        }
    }
}
Also used : Account(com.zimbra.cs.account.Account) InviteChanges(com.zimbra.cs.mailbox.calendar.InviteChanges) ParsedDuration(com.zimbra.common.calendar.ParsedDuration) ArrayList(java.util.ArrayList) RecurId(com.zimbra.cs.mailbox.calendar.RecurId) IRecurrence(com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence) Iterator(java.util.Iterator) ParsedDateTime(com.zimbra.common.calendar.ParsedDateTime) SimpleRepeatingRule(com.zimbra.cs.mailbox.calendar.Recurrence.SimpleRepeatingRule) CalendarResource(com.zimbra.cs.account.CalendarResource) HashSet(java.util.HashSet) RecurrenceRule(com.zimbra.cs.mailbox.calendar.Recurrence.RecurrenceRule) ZOrganizer(com.zimbra.cs.mailbox.calendar.ZOrganizer) IOException(java.io.IOException) ZRecur(com.zimbra.cs.mailbox.calendar.ZRecur) Alarm(com.zimbra.cs.mailbox.calendar.Alarm) ZAttendee(com.zimbra.cs.mailbox.calendar.ZAttendee) ZProperty(com.zimbra.common.calendar.ZCalendar.ZProperty) Frequency(com.zimbra.cs.mailbox.calendar.ZRecur.Frequency) Invite(com.zimbra.cs.mailbox.calendar.Invite) ICalTimeZone(com.zimbra.common.calendar.ICalTimeZone)

Aggregations

Alarm (com.zimbra.cs.mailbox.calendar.Alarm)11 Invite (com.zimbra.cs.mailbox.calendar.Invite)6 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)4 Element (com.zimbra.common.soap.Element)4 ZAttendee (com.zimbra.cs.mailbox.calendar.ZAttendee)4 ParsedDuration (com.zimbra.common.calendar.ParsedDuration)3 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)3 IRecurrence (com.zimbra.cs.mailbox.calendar.Recurrence.IRecurrence)3 ZOrganizer (com.zimbra.cs.mailbox.calendar.ZOrganizer)3 Geo (com.zimbra.common.calendar.Geo)2 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)2 Account (com.zimbra.cs.account.Account)2 Mountpoint (com.zimbra.cs.mailbox.Mountpoint)2 InviteInfo (com.zimbra.cs.mailbox.calendar.InviteInfo)2 Recurrence (com.zimbra.cs.mailbox.calendar.Recurrence)2 IOException (java.io.IOException)2 ICalTimeZone (com.zimbra.common.calendar.ICalTimeZone)1 TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)1 ServiceException (com.zimbra.common.service.ServiceException)1 CalendarResource (com.zimbra.cs.account.CalendarResource)1