private String getText(CalendarItem calItem, Invite invite, Locale locale, TimeZone tz)
      throws ServiceException {
    DateFormat dateTimeFormat =
        DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale);
    dateTimeFormat.setTimeZone(tz);
    DateFormat onlyDateFormat = DateFormat.getDateInstance(DateFormat.SHORT, locale);
    onlyDateFormat.setTimeZone(tz);

    String formattedStart;
    String formattedEnd;
    if (calItem.getType() == MailItem.Type.APPOINTMENT) {
      Date start = new Date(new Long(getProperty(NEXT_INST_START_PROP_NAME)));
      formattedStart = dateTimeFormat.format(start);
      Date end = invite.getEffectiveDuration().addToDate(start);
      formattedEnd = dateTimeFormat.format(end);
    } else {
      // start date and due date is optional for tasks
      formattedStart =
          invite.getStartTime() == null
              ? ""
              : onlyDateFormat.format(invite.getStartTime().getDate());
      formattedEnd =
          invite.getEndTime() == null ? "" : onlyDateFormat.format(invite.getEndTime().getDate());
    }

    String location = invite.getLocation();
    if (StringUtil.isNullOrEmpty(location))
      location = L10nUtil.getMessage(L10nUtil.MsgKey.noLocation);

    String organizer = null;
    ZOrganizer zOrganizer = invite.getOrganizer();
    if (zOrganizer != null)
      organizer = zOrganizer.hasCn() ? zOrganizer.getCn() : zOrganizer.getAddress();
    if (organizer == null) organizer = "";

    String folder = calItem.getMailbox().getFolderById(null, calItem.getFolderId()).getName();

    return L10nUtil.getMessage(
        calItem.getType() == MailItem.Type.APPOINTMENT
            ? L10nUtil.MsgKey.apptReminderSmsText
            : L10nUtil.MsgKey.taskReminderSmsText,
        locale,
        calItem.getSubject(),
        formattedStart,
        formattedEnd,
        location,
        organizer,
        folder);
  }
Example #2
0
 @Override
 public CalendarItem getCalendarItemByUid(OperationContext octxt, String uid)
     throws ServiceException {
   lock.lock();
   try {
     CalendarItem item = super.getCalendarItemByUid(octxt, uid);
     if (item == null
         && Db.supports(Capability.CASE_SENSITIVE_COMPARISON)
         && Invite.isOutlookUid(uid)) {
       if (!uid.toLowerCase().equals(uid)) {
         // maybe stored in db in lower case
         item = super.getCalendarItemByUid(octxt, uid.toLowerCase());
         if (item != null) {
           OfflineLog.offline.debug(
               "coercing Outlook calitem [%d] UID to upper case", item.getId());
           boolean success = false;
           try {
             beginTransaction("forceUpperAppointment", octxt);
             uncache(item);
             DbOfflineMailbox.forceUidUpperCase(this, uid.toLowerCase());
             item = super.getCalendarItemByUid(octxt, uid);
             assert (item != null);
             success = true;
           } finally {
             endTransaction(success);
           }
         }
       } else {
         // possible that we're receiving update from unpatched zcs which is in lower case and our
         // db is
         // already correct return the upper case equivalent; server will keep sending lower until
         // it's
         // patched but should handle pushes in upper since MySQL is case insensitive
         item = super.getCalendarItemByUid(octxt, uid.toUpperCase());
         if (item != null) {
           OfflineLog.offline.debug("received lower case UID but our DB is already in upper case");
         }
       }
     }
     return item;
   } finally {
     lock.release();
   }
 }
  @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);
    ItemIdFormatter ifmt = new ItemIdFormatter(zsc);

    int flags = Flag.toBitmask(request.getAttribute(MailConstants.A_FLAGS, null));
    String[] tags = TagUtil.parseTags(request, mbox, octxt);

    mbox.lock.lock();
    try {
      int defaultFolder =
          getItemType() == MailItem.Type.TASK
              ? Mailbox.ID_FOLDER_TASKS
              : Mailbox.ID_FOLDER_CALENDAR;
      String defaultFolderStr = Integer.toString(defaultFolder);
      String folderIdStr = request.getAttribute(MailConstants.A_FOLDER, defaultFolderStr);
      ItemId iidFolder = new ItemId(folderIdStr, zsc);
      Folder folder = mbox.getFolderById(octxt, iidFolder.getId());
      SetCalendarItemParseResult parsed =
          parseSetAppointmentRequest(request, zsc, octxt, folder, getItemType(), false);

      CalendarItem calItem =
          mbox.setCalendarItem(
              octxt,
              iidFolder.getId(),
              flags,
              tags,
              parsed.defaultInv,
              parsed.exceptions,
              parsed.replies,
              parsed.nextAlarm);

      Element response = getResponseElement(zsc);

      if (parsed.defaultInv != null) {
        response
            .addElement(MailConstants.A_DEFAULT)
            .addAttribute(
                MailConstants.A_ID, ifmt.formatItemId(parsed.defaultInv.invite.getMailItemId()));
      }

      if (parsed.exceptions != null) {
        for (SetCalendarItemData cur : parsed.exceptions) {
          Element e = response.addElement(MailConstants.E_CAL_EXCEPT);
          e.addAttribute(MailConstants.A_CAL_RECURRENCE_ID, cur.invite.getRecurId().toString());
          e.addAttribute(MailConstants.A_ID, ifmt.formatItemId(cur.invite.getMailItemId()));
        }
      }
      String itemId = ifmt.formatItemId(calItem == null ? 0 : calItem.getId());
      response.addAttribute(MailConstants.A_CAL_ID, itemId);
      try {
        Element inv =
            request
                .getElement(MailConstants.A_DEFAULT)
                .getElement(MailConstants.E_MSG)
                .getElement(MailConstants.E_INVITE);
        Element comp = inv.getOptionalElement(MailConstants.E_INVITE_COMPONENT);
        if (comp != null) {
          inv = comp;
        }
        String reqCalItemId = inv.getAttribute(MailConstants.A_CAL_ID);
        String uid = inv.getAttribute(MailConstants.A_UID);
        boolean uidSame =
            (calItem == null
                || (calItem.getUid() == null && uid == null)
                || (calItem.getUid() != null
                    && (calItem.getUid().equals(uid)
                        || (Invite.isOutlookUid(calItem.getUid())
                            && calItem
                                .getUid()
                                .equalsIgnoreCase(
                                    uid))))); // new or same as requested, or Outlook and
        // case-insensitive equal
        if (ZimbraLog.calendar.isInfoEnabled()) {
          StringBuilder logBuf = new StringBuilder();
          if (!reqCalItemId.equals(itemId)) {
            logBuf
                .append("Mapped requested id ")
                .append(reqCalItemId)
                .append(" -> ")
                .append(itemId);
          }
          if (!uidSame) {
            logBuf
                .append(" ?? requested UID ")
                .append(uid)
                .append(" differs from mapped ")
                .append(calItem.getUid());
            ZimbraLog.calendar.warn(logBuf.toString());
          } else if (logBuf.length() > 0) {
            ZimbraLog.calendar.info(logBuf.toString());
          }
        }
        assert (uidSame);
      } catch (ServiceException se) {
        // one of the elements we wanted to use doesn't exist; ignore; no log/assertion possible
      }
      if (!parsed.isTodo)
        response.addAttribute(MailConstants.A_APPT_ID_DEPRECATE_ME, itemId); // for backward compat

      return response;
    } finally {
      mbox.lock.release();
    }
  }
  public static SetCalendarItemParseResult parseSetAppointmentRequest(
      Element request,
      ZimbraSoapContext zsc,
      OperationContext octxt,
      Folder folder,
      MailItem.Type type,
      boolean parseIds)
      throws ServiceException {
    Account acct = getRequestedAccount(zsc);
    Mailbox mbox = getRequestedMailbox(zsc);

    SetCalendarItemParseResult result = new SetCalendarItemParseResult();
    ArrayList<SetCalendarItemData> exceptions = new ArrayList<SetCalendarItemData>();
    Invite defInv = null;

    // First, the <default>
    {
      Element e = request.getOptionalElement(MailConstants.A_DEFAULT);
      if (e != null) {
        result.defaultInv =
            getSetCalendarItemData(
                zsc,
                octxt,
                acct,
                mbox,
                e,
                new SetCalendarItemInviteParser(false, false, folder, type));
        defInv = result.defaultInv.invite;
      }
    }

    // for each <except>
    for (Element e : request.listElements(MailConstants.E_CAL_EXCEPT)) {
      SetCalendarItemData exDat =
          getSetCalendarItemData(
              zsc,
              octxt,
              acct,
              mbox,
              e,
              new SetCalendarItemInviteParser(true, false, folder, type));
      exceptions.add(exDat);
      if (defInv == null) {
        defInv = exDat.invite;
      }
    }

    // for each <cancel>
    for (Element e : request.listElements(MailConstants.E_CAL_CANCEL)) {
      SetCalendarItemData exDat =
          getSetCalendarItemData(
              zsc, octxt, acct, mbox, e, new SetCalendarItemInviteParser(true, true, folder, type));
      exceptions.add(exDat);
      if (defInv == null) {
        defInv = exDat.invite;
      }
    }

    if (exceptions.size() > 0) {
      result.exceptions = new SetCalendarItemData[exceptions.size()];
      exceptions.toArray(result.exceptions);
    } else {
      if (result.defaultInv == null)
        throw ServiceException.INVALID_REQUEST("No default/except/cancel specified", null);
    }

    // <replies>
    Element repliesElem = request.getOptionalElement(MailConstants.E_CAL_REPLIES);
    if (repliesElem != null)
      result.replies = CalendarUtils.parseReplyList(repliesElem, defInv.getTimeZoneMap());

    result.isTodo = defInv != null && defInv.isTodo();

    boolean noNextAlarm = request.getAttributeBool(MailConstants.A_CAL_NO_NEXT_ALARM, false);
    if (noNextAlarm) result.nextAlarm = CalendarItem.NEXT_ALARM_ALL_DISMISSED;
    else
      result.nextAlarm =
          request.getAttributeLong(
              MailConstants.A_CAL_NEXT_ALARM, CalendarItem.NEXT_ALARM_KEEP_CURRENT);

    return result;
  }
  static SetCalendarItemData getSetCalendarItemData(
      ZimbraSoapContext zsc,
      OperationContext octxt,
      Account acct,
      Mailbox mbox,
      Element e,
      ParseMimeMessage.InviteParser parser)
      throws ServiceException {
    String partStatStr =
        e.getAttribute(MailConstants.A_CAL_PARTSTAT, IcalXmlStrMap.PARTSTAT_NEEDS_ACTION);

    // <M>
    Element msgElem = e.getElement(MailConstants.E_MSG);

    // check to see whether the entire message has been uploaded under separate cover
    String attachmentId = msgElem.getAttribute(MailConstants.A_ATTACHMENT_ID, null);
    Element contentElement = msgElem.getOptionalElement(MailConstants.E_CONTENT);

    InviteParserResult ipr = null;

    MimeMessage mm = null;
    if (attachmentId != null) {
      ParseMimeMessage.MimeMessageData mimeData = new ParseMimeMessage.MimeMessageData();
      mm = SendMsg.parseUploadedMessage(zsc, attachmentId, mimeData);
    } else if (contentElement != null) {
      mm = ParseMimeMessage.importMsgSoap(msgElem);
    } else {
      CalSendData dat = handleMsgElement(zsc, octxt, msgElem, acct, mbox, parser);
      mm = dat.mMm;
      ipr = parser.getResult();
    }

    if (ipr == null && msgElem.getOptionalElement(MailConstants.E_INVITE) != null) {
      ipr = parser.parse(zsc, octxt, mbox.getAccount(), msgElem.getElement(MailConstants.E_INVITE));
      // Get description texts out of the MimeMessage and set in the parsed invite.  Do it only if
      // the MimeMessage has text parts.  This prevents discarding texts when they're specified only
      // in the <inv> but not in mime parts.
      if (ipr != null && ipr.mInvite != null && mm != null) {
        String desc = Invite.getDescription(mm, MimeConstants.CT_TEXT_PLAIN);
        String descHtml = Invite.getDescription(mm, MimeConstants.CT_TEXT_HTML);
        if ((desc != null && desc.length() > 0) || (descHtml != null && descHtml.length() > 0))
          ipr.mInvite.setDescription(desc, descHtml);
      }
    }

    ParsedMessage pm = new ParsedMessage(mm, mbox.attachmentsIndexingEnabled());

    Invite inv = (ipr == null ? null : ipr.mInvite);
    if (inv == null || inv.getDTStamp() == -1) { // zdsync if -1 for 4.5 back compat
      CalendarPartInfo cpi = pm.getCalendarPartInfo();
      ZVCalendar cal = null;
      if (cpi != null && CalendarItem.isAcceptableInvite(mbox.getAccount(), cpi)) cal = cpi.cal;
      if (cal == null)
        throw ServiceException.FAILURE("SetCalendarItem could not build an iCalendar object", null);
      boolean sentByMe = false; // not applicable in the SetCalendarItem case
      Invite iCalInv = Invite.createFromCalendar(acct, pm.getFragment(), cal, sentByMe).get(0);

      if (inv == null) {
        inv = iCalInv;
      } else {
        inv.setDtStamp(iCalInv.getDTStamp()); // zdsync
        inv.setFragment(iCalInv.getFragment()); // zdsync
      }
    }
    inv.setPartStat(partStatStr);

    SetCalendarItemData sadata = new SetCalendarItemData();
    sadata.invite = inv;
    sadata.message = pm;
    return sadata;
  }