Exemplo n.º 1
0
 /**
  * Translate a delimted list of tag names to a delimited list of correponding local tagIds
  *
  * @throws ServiceException
  */
 public String localTagsFromNames(String tagNames, String inDelim, String outDelim)
     throws ServiceException {
   if (tagNames != null && tagNames.length() > 0) {
     StringBuilder sb = new StringBuilder();
     String[] names = tagNames.split(inDelim);
     for (String name : names) {
       if (name.trim().length() <= 0) {
         continue;
       }
       Integer tagId = localIdsByName.get(name);
       if (tagId == null) {
         try {
           Tag tag = mbox.getTagByName(null, name);
           tagId = tag.getId();
           localIdsByName.put(name, tagId);
         } catch (MailServiceException mse) {
           if (MailServiceException.NO_SUCH_TAG.equals(mse.getCode())) {
             OfflineLog.offline.debug(
                 "message has tag [" + name + "] which is not visible locally");
             continue;
           } else {
             throw mse;
           }
         }
       }
       sb.append(tagId).append(outDelim);
     }
     if (sb.length() >= outDelim.length()) {
       sb.setLength(sb.length() - outDelim.length());
     }
     return sb.toString();
   } else {
     return tagNames;
   }
 }
Exemplo n.º 2
0
 @Override
 public synchronized void unlock(
     OperationContext octxt, int itemId, MailItem.Type type, String accountId)
     throws ServiceException {
   Account acct = getLockAccount(accountId);
   boolean success = false;
   try {
     beginTransaction("unlock", octxt);
     MailItem item = getItemById(itemId, type);
     if (acct == null && item instanceof Document) {
       // if owner and accountId are the same it's ok
       // hacky like the lock code, but this case is just when lock owned by grantee outside ZD
       Document doc = (Document) item;
       if (doc.lockOwner == null) return;
       if (!doc.lockOwner.equalsIgnoreCase(accountId)) {
         throw MailServiceException.CANNOT_UNLOCK(doc.mId, doc.lockOwner);
       }
       doc.lockOwner = null;
       doc.lockTimestamp = 0;
       doc.markItemModified(Change.LOCK);
       doc.saveMetadata();
     } else {
       item.unlock(acct);
     }
     success = true;
   } finally {
     endTransaction(success);
   }
 }
Exemplo n.º 3
0
 @Override
 public synchronized MailItem lock(
     OperationContext octxt, int itemId, MailItem.Type type, String accountId)
     throws ServiceException {
   Account acct = getLockAccount(accountId);
   boolean success = false;
   try {
     beginTransaction("lock", octxt);
     MailItem item = getItemById(itemId, type);
     if (acct == null && item instanceof Document) {
       // bit of a hack here; basically we need to be able to record lock owner as a remote acct
       // since it can be locked by a grantee that does not exist in ZD
       // rather than setting up a fake account we'll just manually set lock owner
       Document doc = (Document) item;
       if (doc.lockOwner != null && !doc.lockOwner.equalsIgnoreCase(accountId)) {
         throw MailServiceException.CANNOT_LOCK(doc.mId, doc.lockOwner);
       }
       doc.lockOwner = accountId;
       doc.lockTimestamp = System.currentTimeMillis();
       doc.markItemModified(Change.LOCK);
       doc.saveMetadata();
     } else {
       item.lock(acct);
     }
     success = true;
     return item;
   } finally {
     endTransaction(success);
   }
 }
Exemplo n.º 4
0
  /**
   * Determines the set of {@link Message}s to be deleted from this <code>Conversation</code>.
   * Assembles a new {@link PendingDelete} object encapsulating the data on the items to be deleted.
   *
   * <p>A message will be deleted unless:
   *
   * <ul>
   *   <li>The caller lacks {@link ACL#RIGHT_DELETE} permission on the <code>Message</code>.
   *   <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the
   *       <code>Message</code>.
   *   <li>The caller has specified the maximum change number they know about, and the
   *       (modification/content) change number on the <code>Message</code> is greater.
   * </ul>
   *
   * As a result of all these constraints, no messages may actually be deleted.
   *
   * @throws ServiceException The following error codes are possible:
   *     <ul>
   *       <li><code>mail.MODIFY_CONFLICT</code> - if the caller specified a max change number and a
   *           modification check, and the modified change number of the <code>Message</code> is
   *           greater
   *       <li><code>service.FAILURE</code> - if there's a database failure fetching the message
   *           list
   *     </ul>
   */
  @Override
  PendingDelete getDeletionInfo() throws ServiceException {
    PendingDelete info = new PendingDelete();
    info.rootId = mId;
    info.itemIds.add(getType(), mId);

    if (mData.size == 0) return info;
    List<Message> msgs = getMessages();
    TargetConstraint tcon = mMailbox.getOperationTargetConstraint();

    boolean excludeModify = false, excludeAccess = false;
    for (Message child : msgs) {
      // silently skip explicitly excluded messages, PERMISSION_DENIED messages, and MODIFY_CONFLICT
      // messages
      if (!TargetConstraint.checkItem(tcon, child)) continue;
      else if (!child.canAccess(ACL.RIGHT_DELETE)) excludeAccess = true;
      else if (!child.checkChangeID()) excludeModify = true;
      else info.add(child.getDeletionInfo());
    }

    int totalDeleted = info.itemIds.size();
    if (totalDeleted == 1) {
      // if all messages have been excluded, some for "error" reasons, throw an exception
      if (excludeAccess)
        throw ServiceException.PERM_DENIED("you do not have sufficient permissions");
      if (excludeModify) throw MailServiceException.MODIFY_CONFLICT();
    }
    if (totalDeleted != msgs.size() + 1) info.incomplete = true;
    return info;
  }
Exemplo n.º 5
0
  /** please call this *after* adding the child row to the DB */
  @Override
  void addChild(MailItem child) throws ServiceException {
    if (!(child instanceof Message)) throw MailServiceException.CANNOT_PARENT();
    Message msg = (Message) child;

    super.addChild(msg);

    // update inherited flags
    int oldFlags = mData.flags;
    mData.flags |= msg.getInternalFlagBitmask();
    if (mData.flags != oldFlags) markItemModified(Change.MODIFIED_FLAGS);

    // update inherited tags
    long oldTags = mData.tags;
    mData.tags |= msg.getTagBitmask();
    if (mData.tags != oldTags) markItemModified(Change.MODIFIED_TAGS);

    // update unread counts
    if (msg.isUnread()) {
      markItemModified(Change.MODIFIED_UNREAD);
      updateUnread(
          child.mData.unreadCount,
          child.isTagged(Flag.ID_FLAG_DELETED) ? child.mData.unreadCount : 0);
    }

    markItemModified(Change.MODIFIED_SIZE | Change.MODIFIED_SENDERS | Change.MODIFIED_METADATA);

    MetadataCallback.duringConversationAdd(mExtendedData, msg);

    // FIXME: this ordering is to work around the fact that when getSenderList has to
    //   recalc the metadata, it uses the already-updated DB message state to do it...
    mData.date = mMailbox.getOperationTimestamp();
    mData.contentChanged(mMailbox);

    if (!mMailbox.hasListeners(Session.Type.SOAP)) {
      instantiateSenderList();
      mData.size++;
      try {
        if (mSenderList != null) mSenderList.add(msg);
      } catch (SenderList.RefreshException slre) {
        mSenderList = null;
      }
      saveMetadata();
    } else {
      boolean recalculated = loadSenderList();
      if (!recalculated) {
        mData.size++;
        try {
          mSenderList.add(msg);
          saveMetadata();
        } catch (SenderList.RefreshException slre) {
          recalculateMetadata();
        }
      }
    }
  }
Exemplo n.º 6
0
  void setConversationId(OperationContext octxt, int msgId, int convId) throws ServiceException {
    // we're not allowing any magic -- we are being completely literal about the target conv id
    if (convId <= 0 && convId != -msgId) {
      throw MailServiceException.NO_SUCH_CONV(convId);
    }
    boolean success = false;
    try {
      beginTransaction("setConversationId", octxt);

      Message msg = getMessageById(msgId);
      Conversation oldConv = (Conversation) msg.getParent();
      if (convId == msg.getConversationId()) {
        success = true;
        if (oldConv != null
            && (oldConv.getSize() < 1 || oldConv.getUnreadCount() < msg.getUnreadCount())) {
          OfflineLog.offline.error(
              "Conversation size/unread inconsistent for conversation " + oldConv);
        }
        return;
      }

      try {
        Conversation newConv;
        if (convId <= 0) {
          // moving from a real conv to a virtual one
          newConv = VirtualConversation.create(this, msg);
        } else {
          // moving to an existing real conversation
          newConv = getConversationById(convId);
        }
        DbMailItem.setParent(msg, newConv);
        if (convId > 0) {
          newConv.addChild(msg);
        }
        msg.markItemModified(Change.PARENT);
        msg.mData.parentId = convId;
        msg.metadataChanged();
      } catch (MailServiceException.NoSuchItemException nsie) {
        // real conversation didn't exist; create it!
        createConversation(convId, msg);
      }

      // and now we can update (and possibly delete) the old conversation
      oldConv.removeChild(msg);

      success = true;
    } finally {
      endTransaction(success);
    }
  }
Exemplo n.º 7
0
 /**
  * Create a mapping from remote to local tagId
  *
  * @throws ServiceException
  */
 public void mapTag(int remote, int id) throws ServiceException {
   if (!validateId(id)) {
     throw MailServiceException.NO_SUCH_TAG(id);
   }
   mapTagInternal(remote, id);
 }
Exemplo n.º 8
0
  /**
   * Moves all the conversation's {@link Message}s to a different {@link Folder}. Persists the
   * change to the database and the in-memory cache. Updates all relevant unread counts, folder
   * sizes, etc.
   *
   * <p>Messages moved to the Trash folder are automatically marked read. Conversations moved to the
   * Junk folder will not receive newly-delivered messages.
   *
   * <p>Messages in the conversation are omitted from this operation if one or more of the following
   * applies:
   *
   * <ul>
   *   <li>The caller lacks {@link ACL#RIGHT_WRITE} permission on the <code>Message</code>.
   *   <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the
   *       <code>Message</code>.
   *   <li>The caller has specified the maximum change number they know about, and the
   *       (modification/content) change number on the <code>Message</code> is greater.
   * </ul>
   *
   * As a result of all these constraints, no messages may actually be moved.
   *
   * @perms {@link ACL#RIGHT_INSERT} on the target folder, {@link ACL#RIGHT_DELETE} on the messages'
   *     source folders
   */
  @Override
  boolean move(Folder target) throws ServiceException {
    if (!target.canContain(TYPE_MESSAGE)) throw MailServiceException.CANNOT_CONTAIN();
    markItemModified(Change.UNMODIFIED);

    List<Message> msgs = getMessages();
    TargetConstraint tcon = mMailbox.getOperationTargetConstraint();
    boolean toTrash = target.inTrash();
    int oldUnread = 0;
    for (Message msg : msgs) if (msg.isUnread()) oldUnread++;
    // if mData.unread is wrong, what to do?  right now, always use the calculated value
    mData.unreadCount = oldUnread;

    boolean excludeAccess = false;

    List<Integer> markedRead = new ArrayList<Integer>();
    List<Message> moved = new ArrayList<Message>();
    List<Message> indexUpdated = new ArrayList<Message>();

    for (Message msg : msgs) {
      Folder source = msg.getFolder();

      // skip messages that don't need to be moved, or that the client can't modify, doesn't know
      // about, or has explicitly excluded
      if (source.getId() == target.getId()) {
        continue;
      } else if (!source.canAccess(ACL.RIGHT_DELETE)) {
        excludeAccess = true;
        continue;
      } else if (target.getId() != Mailbox.ID_FOLDER_TRASH
          && target.getId() != Mailbox.ID_FOLDER_SPAM
          && !target.canAccess(ACL.RIGHT_INSERT)) {
        excludeAccess = true;
        continue;
      } else if (!msg.checkChangeID() || !TargetConstraint.checkItem(tcon, msg)) {
        continue;
      }

      boolean isDeleted = msg.isTagged(Flag.ID_FLAG_DELETED);
      if (msg.isUnread()) {
        if (!toTrash || msg.inTrash()) {
          source.updateUnread(-1, isDeleted ? -1 : 0);
          target.updateUnread(1, isDeleted ? 1 : 0);
        } else {
          // unread messages moved from Mailbox to Trash need to be marked read:
          //   update cached unread counts (message, conversation, folder, tags)
          msg.updateUnread(-1, isDeleted ? -1 : 0);
          //   note that we need to update this message in the DB
          markedRead.add(msg.getId());
        }
      }

      // moved an item out of the spam folder, need to index it
      if (msg.inSpam() && !target.inSpam()) {
        if (msg.isIndexed() && msg.getIndexId() != -1) {
          msg.indexIdChanged(msg.getId());
          indexUpdated.add(msg);
        }
      }

      // if a draft is being moved to Trash then remove any "send-later" info from it
      if (toTrash && msg.isDraft()) msg.setDraftAutoSendTime(0);

      // handle folder message counts
      source.updateSize(-1, isDeleted ? -1 : 0, -msg.getTotalSize());
      target.updateSize(1, isDeleted ? 1 : 0, msg.getTotalSize());

      moved.add(msg);
      msg.folderChanged(target, 0);
    }

    // mark unread messages moved from Mailbox to Trash/Spam as read in the DB
    if (!markedRead.isEmpty()) DbMailItem.alterUnread(target.getMailbox(), markedRead, false);

    if (moved.isEmpty()) {
      if (excludeAccess)
        throw ServiceException.PERM_DENIED("you do not have sufficient permissions");
    } else {
      // moving a conversation to spam closes it
      if (target.inSpam()) detach();
      if (ZimbraLog.mailop.isInfoEnabled()) {
        StringBuilder ids = new StringBuilder();
        for (int i = 0; i < moved.size(); i++) {
          if (i > 0) {
            ids.append(',');
          }
          ids.append(moved.get(i).getId());
        }
        ZimbraLog.mailop.info(
            "Moving %s to %s.  Affected message ids: %s.",
            getMailopContext(this), getMailopContext(target), ids);
      }
      DbMailItem.setFolder(moved, target);

      if (!indexUpdated.isEmpty()) {
        DbMailItem.setIndexIds(mMailbox, indexUpdated);
        for (Message msg : indexUpdated) {
          mMailbox.queueForIndexing(msg, false, null);
        }
      }
    }

    return !moved.isEmpty();
  }
Exemplo n.º 9
0
  /**
   * Tags or untags all messages in the conversation. Persists the change to the database and cache.
   * If the conversation includes at least one unread {@link Message} whose tagged state is
   * changing, updates the {@link Tag}'s unread count appropriately.
   *
   * <p>Messages in the conversation are omitted from this operation if one or more of the following
   * applies:
   *
   * <ul>
   *   <li>The caller lacks {@link ACL#RIGHT_WRITE} permission on the <code>Message</code>.
   *   <li>The caller has specified a {@link MailItem.TargetConstraint} that explicitly excludes the
   *       <code>Message</code>.
   *   <li>The caller has specified the maximum change number they know about, and the
   *       (modification/content) change number on the <code>Message</code> is greater.
   * </ul>
   *
   * As a result of all these constraints, no messages may actually be tagged/untagged.
   *
   * @perms {@link ACL#RIGHT_WRITE} on all the messages
   */
  @Override
  void alterTag(Tag tag, boolean add) throws ServiceException {
    if (tag == null) throw ServiceException.FAILURE("missing tag argument", null);
    if (!add && !isTagged(tag)) return;
    if (tag.getId() == Flag.ID_FLAG_UNREAD)
      throw ServiceException.FAILURE("unread state must be set with alterUnread", null);
    // don't let the user tag things as "has attachments" or "draft"
    if (tag instanceof Flag && (tag.getBitmask() & Flag.FLAG_SYSTEM) != 0)
      throw MailServiceException.CANNOT_TAG(tag, this);

    markItemModified(tag instanceof Flag ? Change.MODIFIED_FLAGS : Change.MODIFIED_TAGS);

    TargetConstraint tcon = mMailbox.getOperationTargetConstraint();
    boolean excludeAccess = false;

    List<Message> msgs = getMessages();
    List<Integer> targets = new ArrayList<Integer>(msgs.size());
    for (Message msg : msgs) {
      // skip messages that don't need to be changed, or that the client can't modify, doesn't know
      // about, or has explicitly excluded
      if (msg.isTagged(tag) == add) {
        continue;
      } else if (!msg.canAccess(ACL.RIGHT_WRITE)) {
        excludeAccess = true;
        continue;
      } else if (!msg.checkChangeID() || !TargetConstraint.checkItem(tcon, msg)) {
        continue;
      } else if (add && !tag.canTag(msg)) {
        throw MailServiceException.CANNOT_TAG(tag, this);
      }

      targets.add(msg.getId());
      msg.tagChanged(tag, add);

      // since we're adding/removing a tag, the tag's unread count may change
      int delta = add ? 1 : -1;
      if (tag.trackUnread() && msg.isUnread())
        tag.updateUnread(delta, isTagged(Flag.ID_FLAG_DELETED) ? delta : 0);

      // if we're adding/removing the \Deleted flag, update the folder and tag "deleted" and
      // "deleted unread" counts
      if (tag.getId() == Flag.ID_FLAG_DELETED) {
        getFolder().updateSize(0, delta, 0);
        // note that Message.updateUnread() calls updateTagUnread()
        if (msg.isUnread()) msg.updateUnread(0, delta);
      }
    }

    if (targets.isEmpty()) {
      if (excludeAccess)
        throw ServiceException.PERM_DENIED("you do not have sufficient permissions");
    } else {
      if (ZimbraLog.mailop.isDebugEnabled()) {
        String operation = add ? "Setting" : "Unsetting";
        ZimbraLog.mailop.debug(
            "%s %s for %s.  Affected ids: %s",
            operation,
            getMailopContext(tag),
            getMailopContext(this),
            StringUtil.join(",", targets));
      }
      recalculateCounts(msgs);
      DbMailItem.alterTag(tag, targets, add);
    }
  }