Search in sources :

Example 21 with TimeZoneMap

use of com.zimbra.common.calendar.TimeZoneMap 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 22 with TimeZoneMap

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

the class FreeBusy method toVCalendarAsVEvents.

public String toVCalendarAsVEvents() throws ServiceException {
    StringWriter writer = new StringWriter();
    String publish = ICalTok.PUBLISH.toString();
    long now = System.currentTimeMillis();
    writer.append("BEGIN:VCALENDAR").append(NL);
    writer.append("PRODID:").append(ZCalendar.sZimbraProdID).append(NL);
    writer.append("VERSION:").append(ZCalendar.sIcalVersion).append(NL);
    writer.append("METHOD:").append(publish).append(NL);
    String uidBase = "tmp_" + LdapUtil.generateUUID() + "_";
    int uidCount = 0;
    TimeZoneMap tzMap = new TimeZoneMap(ICalTimeZone.getUTC());
    for (Iterator<Interval> iter = this.iterator(); iter.hasNext(); ) {
        FreeBusy.Interval cur = iter.next();
        String status = cur.getStatus();
        if (status.equals(IcalXmlStrMap.FBTYPE_FREE)) {
            continue;
        } else if (status.equals(IcalXmlStrMap.FBTYPE_NODATA)) {
            // Treat no-data case same as free, because other apps probably won't understand a non-standard fbtype value.
            continue;
        }
        Invite inv = new Invite(publish, tzMap, true);
        inv.setUid(uidBase + (++uidCount));
        inv.setSeqNo(0);
        inv.setDtStamp(now);
        inv.setDtStart(ParsedDateTime.fromUTCTime(cur.getStart()));
        inv.setDtEnd(ParsedDateTime.fromUTCTime(cur.getEnd()));
        inv.setFreeBusy(status);
        if (status.equals(IcalXmlStrMap.FBTYPE_BUSY_TENTATIVE)) {
            inv.setStatus(IcalXmlStrMap.STATUS_TENTATIVE);
        } else if (status.equals(IcalXmlStrMap.FBTYPE_BUSY_UNAVAILABLE)) {
            inv.setStatus(IcalXmlStrMap.STATUS_CONFIRMED);
        } else {
            // busy
            inv.setStatus(IcalXmlStrMap.STATUS_CONFIRMED);
        }
        ZComponent comp = inv.newToVComponent(false, true);
        try {
            comp.toICalendar(writer);
        } catch (IOException e) {
            throw ServiceException.FAILURE("can't write iCalendar object", e);
        }
    }
    writer.append("END:VCALENDAR").append(NL);
    return writer.toString();
}
Also used : ZComponent(com.zimbra.common.calendar.ZCalendar.ZComponent) StringWriter(java.io.StringWriter) TimeZoneMap(com.zimbra.common.calendar.TimeZoneMap) IOException(java.io.IOException) Invite(com.zimbra.cs.mailbox.calendar.Invite)

Example 23 with TimeZoneMap

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

the class CheckRecurConflicts method handle.

@Override
public Element handle(Element request, Map<String, Object> context) throws ServiceException {
    ZimbraSoapContext zsc = getZimbraSoapContext(context);
    Account authAcct = getAuthenticatedAccount(zsc);
    Element response = getResponseElement(zsc);
    long rangeStart = request.getAttributeLong(MailConstants.A_CAL_START_TIME, 0);
    if (rangeStart == 0)
        rangeStart = System.currentTimeMillis();
    long rangeEnd = request.getAttributeLong(MailConstants.A_CAL_END_TIME, 0);
    if (rangeEnd == 0)
        rangeEnd = Long.MAX_VALUE;
    boolean allInstances = request.getAttributeBool(MailConstants.A_CAL_ALL, false);
    String exApptUid = request.getAttribute(MailConstants.A_APPT_FREEBUSY_EXCLUDE_UID, null);
    // Parse and expand the recurrence.
    TimeZoneMap tzmap = new TimeZoneMap(Util.getAccountTimeZone(authAcct));
    ParsedRecurrence parsed = parseRecur(request, tzmap);
    List<Instance> instances = getInstances(parsed, rangeStart, rangeEnd);
    if (instances == null || instances.isEmpty())
        return response;
    // Find the range covered by the instances.
    long rangeStartActual = rangeEnd, rangeEndActual = rangeStart;
    for (Instance inst : instances) {
        if (inst.hasStart() && inst.hasEnd()) {
            rangeStartActual = Math.min(rangeStartActual, inst.getStart());
            rangeEndActual = Math.max(rangeEndActual, inst.getEnd());
        }
    }
    if (rangeStartActual >= rangeEndActual)
        return response;
    // Run free/busy search on the users.
    FreeBusyQuery fbQuery = new FreeBusyQuery((HttpServletRequest) context.get(SoapServlet.SERVLET_REQUEST), zsc, authAcct, rangeStartActual, rangeEndActual, exApptUid);
    for (Iterator<Element> usrIter = request.elementIterator(MailConstants.E_FREEBUSY_USER); usrIter.hasNext(); ) {
        Element usrElem = usrIter.next();
        int folderId = (int) usrElem.getAttributeLong(MailConstants.A_FOLDER, FreeBusyQuery.CALENDAR_FOLDER_ALL);
        if (folderId == Mailbox.ID_FOLDER_USER_ROOT || folderId == 0)
            folderId = FreeBusyQuery.CALENDAR_FOLDER_ALL;
        String id = usrElem.getAttribute(MailConstants.A_ID, null);
        if (id != null)
            fbQuery.addAccountId(id, folderId);
        String name = usrElem.getAttribute(MailConstants.A_NAME, null);
        if (name != null)
            fbQuery.addEmailAddress(name, folderId);
    }
    Collection<FreeBusy> fbResults = fbQuery.getResults();
    List<UserConflicts> conflicts = new ArrayList<UserConflicts>();
    for (FreeBusy fb : fbResults) {
        UserConflicts ucon = getConflicts(fb, instances);
        conflicts.add(ucon);
    }
    // Find conflicts for each instance.
    for (Instance inst : instances) {
        Element instElem = addInstance(response, inst);
        int numConflicts = 0;
        for (UserConflicts ucon : conflicts) {
            String fbStatus = ucon.get(inst);
            if (fbStatus != null) {
                ++numConflicts;
                String username = ucon.getUsername();
                Element usrElem = instElem.addElement(MailConstants.E_FREEBUSY_USER);
                usrElem.addAttribute(MailConstants.A_NAME, username);
                usrElem.addAttribute(MailConstants.A_APPT_FREEBUSY, fbStatus);
            }
        }
        if (numConflicts == 0 && !allInstances)
            instElem.detach();
    }
    return response;
}
Also used : Account(com.zimbra.cs.account.Account) FreeBusy(com.zimbra.cs.fb.FreeBusy) Instance(com.zimbra.cs.mailbox.CalendarItem.Instance) Element(com.zimbra.common.soap.Element) FreeBusyQuery(com.zimbra.cs.fb.FreeBusyQuery) ArrayList(java.util.ArrayList) ZimbraSoapContext(com.zimbra.soap.ZimbraSoapContext) TimeZoneMap(com.zimbra.common.calendar.TimeZoneMap)

Example 24 with TimeZoneMap

use of com.zimbra.common.calendar.TimeZoneMap 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 25 with TimeZoneMap

use of com.zimbra.common.calendar.TimeZoneMap 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)

Aggregations

TimeZoneMap (com.zimbra.common.calendar.TimeZoneMap)30 Invite (com.zimbra.cs.mailbox.calendar.Invite)15 ICalTimeZone (com.zimbra.common.calendar.ICalTimeZone)12 ParsedDateTime (com.zimbra.common.calendar.ParsedDateTime)10 ZVCalendar (com.zimbra.common.calendar.ZCalendar.ZVCalendar)8 Element (com.zimbra.common.soap.Element)8 ZProperty (com.zimbra.common.calendar.ZCalendar.ZProperty)7 Date (java.util.Date)7 Account (com.zimbra.cs.account.Account)6 Mailbox (com.zimbra.cs.mailbox.Mailbox)6 ZimbraSoapContext (com.zimbra.soap.ZimbraSoapContext)6 ArrayList (java.util.ArrayList)6 ParsedDuration (com.zimbra.common.calendar.ParsedDuration)5 ZComponent (com.zimbra.common.calendar.ZCalendar.ZComponent)5 ServiceException (com.zimbra.common.service.ServiceException)5 CalendarItem (com.zimbra.cs.mailbox.CalendarItem)5 Instance (com.zimbra.cs.mailbox.CalendarItem.Instance)5 ItemId (com.zimbra.cs.service.util.ItemId)5 OperationContext (com.zimbra.cs.mailbox.OperationContext)4 RecurId (com.zimbra.cs.mailbox.calendar.RecurId)4