use of com.zimbra.cs.mailbox.Tag in project zm-mailbox by Zimbra.
the class ItemActionTest method deleteAllTagKeepsStatusOfFlags.
@Test
public void deleteAllTagKeepsStatusOfFlags() throws Exception {
//Bug 76781
Account acct = Provisioning.getInstance().get(Key.AccountBy.name, "test@zimbra.com");
Mailbox mbox = MailboxManager.getInstance().getMailboxByAccount(acct);
acct.setMailThreadingAlgorithm(MailThreadingAlgorithm.subject);
// setup: add the root message
ParsedMessage pm = MailboxTestUtil.generateMessage("test subject");
DeliveryOptions dopt = new DeliveryOptions().setFolderId(Mailbox.ID_FOLDER_INBOX);
mbox.addMessage(null, pm, dopt, null);
// add additional messages for conversation
pm = MailboxTestUtil.generateMessage("Re: test subject");
int msgId = mbox.addMessage(null, pm, dopt, null).getId();
// set flag to unread for this message
MailboxTestUtil.setFlag(mbox, msgId, Flag.FlagInfo.UNREAD);
MailItem item = mbox.getItemById(null, msgId, MailItem.Type.UNKNOWN);
// verify message unread flag is set
Assert.assertEquals("Verifying Unread flag is set.", Flag.BITMASK_UNREAD, item.getFlagBitmask());
// add 2 tags
mbox.alterTag(null, msgId, MailItem.Type.MESSAGE, tag1, true, null);
mbox.alterTag(null, msgId, MailItem.Type.MESSAGE, tag2, true, null);
Element request = new Element.XMLElement(MailConstants.ITEM_ACTION_REQUEST);
Element action = request.addElement(MailConstants.E_ACTION);
action.addAttribute(MailConstants.A_OPERATION, ItemAction.OP_UPDATE);
action.addAttribute(MailConstants.A_ITEM_TYPE, "");
action.addAttribute(MailConstants.A_ID, msgId);
new ItemAction().handle(request, ServiceTestUtil.getRequestContext(acct));
Assert.assertEquals("Verifying unread flag is set after tag deletion", Flag.BITMASK_UNREAD, item.getFlagBitmask());
Tag tag = mbox.getTagByName(null, tag1);
Assert.assertEquals(tag1 + " (tag messages)", 0, tag.getSize());
tag = mbox.getTagByName(null, tag2);
Assert.assertEquals(tag1 + " (tag messages)", 0, tag.getSize());
}
use of com.zimbra.cs.mailbox.Tag in project zm-mailbox by Zimbra.
the class ZimbraMailAdapter method setTagsVisible.
private void setTagsVisible(String[] tags) {
for (String tagName : tags) {
try {
Tag tag;
tag = mailbox.getTagByName(null, tagName);
if (tag == null || !tag.isListed()) {
mailbox.createTag(null, tagName, (byte) 0);
}
} catch (ServiceException e) {
ZimbraLog.filter.info("Failed to set tag visible. \"" + tagName + "\" stays invisible on the tag list");
}
}
}
use of com.zimbra.cs.mailbox.Tag in project zm-mailbox by Zimbra.
the class DbSearch method searchInternal.
private List<Result> searchInternal(DbConnection conn, DbSearchConstraints node, SortBy sort, int offset, int limit, FetchMode fetch, boolean searchDraftsSeparately) throws SQLException, ServiceException {
//check if we need to run this as two queries: one with "mi.recipients is not NULL" and one in drafts with "mi.recipients is NULL"
if (searchingInDrafts(node) && searchDraftsSeparately && sort != null && (sort.equals(SortBy.RCPT_ASC) || sort.equals(SortBy.RCPT_DESC))) {
//clone the existing node containing the Drafts constraint
DbSearchConstraints.Leaf draftsConstraint = findDraftsConstraint(node).clone();
for (Folder folder : draftsConstraint.folders) {
if (folder.getId() == Mailbox.ID_FOLDER_DRAFTS) {
draftsConstraint.folders.clear();
//constrain the node to only drafts
draftsConstraint.folders.add(folder);
break;
}
}
draftsConstraint.excludeHasRecipients = true;
DbSearchConstraints node1;
DbSearchConstraints node2;
if (sort.equals(SortBy.RCPT_ASC)) {
node1 = node;
node2 = draftsConstraint;
} else {
node1 = draftsConstraint;
node2 = node;
}
List<Result> result = searchTwoConstraints(conn, node1, node2, sort, offset, limit, fetch);
return result;
}
boolean hasValidLIMIT = offset >= 0 && limit >= 0;
boolean hasMailItemOnlyConstraints = true;
boolean hasAppointmentTableConstraints = hasAppointmentTableConstraints(node);
if (hasAppointmentTableConstraints) {
hasMailItemOnlyConstraints = hasMailItemOnlyConstraints(node);
}
boolean requiresUnion = hasMailItemOnlyConstraints && hasAppointmentTableConstraints;
//HACK!HACK!HACK!
//Bug: 68609
//slow search "in:inbox is:unread or is:flagged"
//its actually cheaper to do a D/B join between mail_item and tagged_item table when
//the unread items are smaller in count. We can possibly do the same for user tags.
boolean joinTaggedItem = false;
if (node instanceof DbSearchConstraints.Leaf && !dumpster) {
DbSearchConstraints.Leaf constraint = node.toLeaf();
if (constraint.excludeTags.isEmpty() && !constraint.tags.isEmpty() && constraint.tags.size() == 1) {
Tag tag = constraint.tags.iterator().next();
if (tag.getId() == FlagInfo.UNREAD.toId() || tag.getId() > 0) {
long count = 0;
if (tag.getId() == FlagInfo.UNREAD.toId()) {
//let's make an estimate of # of unread items for this mailbox.
//It doesn't matter which folder(s) the user is trying to search because
//the performance solely depends on the # of unread items
count = countUnread(mailbox.getFolderById(null, Mailbox.ID_FOLDER_USER_ROOT));
} else if (tag.getId() > 0) {
//user tag
count = tag.getSize();
}
if (count < LC.search_tagged_item_count_join_query_cutoff.intValue())
joinTaggedItem = true;
}
}
}
if (hasMailItemOnlyConstraints) {
if (requiresUnion) {
sql.append('(');
}
boolean maybeExcludeNoRecipients = true;
if (node.toLeaf() != null) {
maybeExcludeNoRecipients = !node.toLeaf().excludeHasRecipients;
}
// SELECT mi.id,... FROM mail_item AS mi [FORCE INDEX (...)] WHERE mi.mailboxid = ? AND
encodeSelect(sort, fetch, false, joinTaggedItem, node, hasValidLIMIT, maybeExcludeNoRecipients);
/*
*( SUB-NODE AND/OR (SUB-NODE...) ) AND/OR ( SUB-NODE ) AND
* (
* one of: [type NOT IN (...)] || [type = ?] || [type IN ( ...)]
* [ AND flags IN (...) ]
* ..etc
* )
*/
encodeConstraint(node, hasAppointmentTableConstraints ? APPOINTMENT_TABLE_TYPES : null, false, joinTaggedItem);
if (requiresUnion) {
sql.append(orderBy(sort, true));
// LIMIT ?, ?
if (hasValidLIMIT && Db.supports(Db.Capability.LIMIT_CLAUSE)) {
sql.append(' ').append(Db.getInstance().limit(offset, limit));
}
}
}
if (requiresUnion) {
sql.append(" ) UNION ALL (");
}
if (hasAppointmentTableConstraints) {
// SELECT...again...(this time with "appointment as ap")...WHERE...
encodeSelect(sort, fetch, true, false, node, hasValidLIMIT);
encodeConstraint(node, APPOINTMENT_TABLE_TYPES, true, false);
if (requiresUnion) {
sql.append(orderBy(sort, true));
// LIMIT ?, ?
if (hasValidLIMIT && Db.supports(Db.Capability.LIMIT_CLAUSE)) {
sql.append(' ').append(Db.getInstance().limit(offset, limit));
}
if (requiresUnion) {
sql.append(')');
}
}
}
// TODO FIXME: include COLLATION for sender/subject sort
sql.append(orderBy(sort, true));
// LIMIT ?, ?
if (hasValidLIMIT && Db.supports(Db.Capability.LIMIT_CLAUSE)) {
sql.append(' ').append(Db.getInstance().limit(offset, limit));
}
if (Db.supports(Db.Capability.SQL_PARAM_LIMIT)) {
Db.getInstance().checkParamLimit(params.size());
}
PreparedStatement stmt = null;
ResultSet rs = null;
try {
// Create the statement and bind all our parameters!
stmt = conn.prepareStatement(sql.toString());
setParameters(stmt);
// Limit query if DB doesn't support LIMIT clause
if (hasValidLIMIT && !Db.supports(Db.Capability.LIMIT_CLAUSE)) {
stmt.setMaxRows(offset + limit + 1);
}
rs = stmt.executeQuery();
List<Result> result = new ArrayList<Result>();
while (rs.next()) {
if (hasValidLIMIT && !Db.supports(Db.Capability.LIMIT_CLAUSE)) {
if (offset-- > 0) {
continue;
}
if (limit-- <= 0) {
break;
}
}
Object sortkey = getSortKey(rs, sort);
switch(fetch) {
case ID:
result.add(new IdResult(rs, sortkey));
break;
case MAIL_ITEM:
result.add(new ItemDataResult(rs, sortkey, dumpster));
break;
case IMAP_MSG:
result.add(new ImapResult(rs, sortkey));
break;
case MODSEQ:
result.add(new ModSeqResult(rs, sortkey));
break;
case MODCONTENT:
result.add(new ModContentResult(rs, sortkey));
break;
case PARENT:
result.add(new ParentResult(rs, sortkey));
break;
default:
assert false : fetch;
}
}
return result;
} finally {
conn.closeQuietly(rs);
conn.closeQuietly(stmt);
}
}
use of com.zimbra.cs.mailbox.Tag in project zm-mailbox by Zimbra.
the class DbSearch method encodeTag.
private boolean encodeTag(Set<Tag> tags, boolean bool, boolean useJoin, boolean and) {
if (tags.isEmpty()) {
return false;
}
if (useJoin) {
assert !dumpster;
assert bool;
}
if (dumpster) {
// There is no corresponding table to TAGGED_ITEM in dumpster, hence brute-force search.
int flags = 0;
for (Tag tag : tags) {
if (tag instanceof Flag) {
Flag flag = (Flag) tag;
if (flag.getId() == Flag.ID_UNREAD) {
if (and) {
sql.append(" AND ");
}
sql.append("mi.unread = ?");
and = true;
params.add(bool ? 1 : 0);
} else {
flags |= flag.toBitmask();
}
} else {
if (and) {
sql.append(" AND ");
}
and = true;
sql.append("(mi.tag_names");
if (!bool) {
// Include NULL because LIKE does not match NULL.
sql.append(" IS NULL OR mi.tag_names NOT");
}
sql.append(" LIKE ?)");
params.add(DbTag.tagLIKEPattern(tag.getName()));
}
}
if (flags != 0) {
sql.append(" AND ").append(Db.getInstance().bitAND("mi.flags", "?")).append(" = ?");
params.add(flags);
params.add(bool ? flags : 0);
}
} else if (bool) {
// Repeats "EXISTS (SELECT...)" as many times as tags.
if (useJoin) {
assert tags.size() == 1;
Tag tag = tags.iterator().next();
//AND (mi.id = ti.item_id AND mi.mailbox_id = ti.mailbox_id AND ti.tag_id = -10)
if (and) {
sql.append(" AND ");
}
sql.append("(mi.id = ti.item_id AND mi.mailbox_id = ti.mailbox_id AND ti.tag_id = ?)");
params.add(tag.getId());
and = true;
} else {
for (Tag tag : tags) {
if (and) {
sql.append(" AND ");
}
sql.append("EXISTS (SELECT * FROM ").append(DbTag.getTaggedItemTableName(mailbox, "ti"));
and = true;
sql.append(" WHERE ");
if (!DebugConfig.disableMailboxGroups) {
sql.append("mi.mailbox_id = ti.mailbox_id AND ");
}
sql.append("mi.id = ti.item_id AND ti.tag_id = ?)");
params.add(tag.getId());
}
}
} else {
// NOT EXISTS (SELECT... WHERE... tag_id IN...)
if (and) {
sql.append(" AND ");
}
sql.append("NOT EXISTS (SELECT * FROM ").append(DbTag.getTaggedItemTableName(mailbox, "ti"));
and = true;
sql.append(" WHERE ");
if (!DebugConfig.disableMailboxGroups) {
sql.append("mi.mailbox_id = ti.mailbox_id AND ");
}
sql.append("mi.id = ti.item_id AND ").append(DbUtil.whereIn("ti.tag_id", tags.size())).append(')');
for (Tag tag : tags) {
params.add(tag.getId());
}
}
return true;
}
use of com.zimbra.cs.mailbox.Tag in project zm-mailbox by Zimbra.
the class ImapHandler method doSTORE.
boolean doSTORE(String tag, String sequenceSet, List<String> flagNames, StoreAction operation, boolean silent, int modseq, boolean byUID) throws IOException, ImapException {
checkCommandThrottle(new StoreCommand(sequenceSet, flagNames, operation, modseq));
if (!checkState(tag, State.SELECTED)) {
return true;
}
ImapFolder i4folder = getSelectedFolder();
if (i4folder == null) {
throw new ImapSessionClosedException();
}
if (!i4folder.isWritable()) {
sendNO(tag, "mailbox selected READ-ONLY");
return true;
}
if (modseq >= 0) {
activateExtension(ImapExtension.CONDSTORE);
}
boolean modseqEnabled = sessionActivated(ImapExtension.CONDSTORE);
// MUST reject any such command with the tagged BAD response."
if (!modseqEnabled && modseq >= 0) {
throw new ImapParseException(tag, "NOMODSEQ", "cannot STORE UNCHANGEDSINCE in this mailbox", true);
}
ImapMessageSet modifyConflicts = modseqEnabled ? new ImapMessageSet() : null;
String command = (byUID ? "UID STORE" : "STORE");
List<Tag> newTags = (operation != StoreAction.REMOVE ? new ArrayList<Tag>() : null);
Mailbox mbox = selectedFolder.getMailbox();
Set<ImapMessage> i4set;
mbox.lock.lock();
try {
i4set = i4folder.getSubsequence(tag, sequenceSet, byUID);
} finally {
mbox.lock.release();
}
boolean allPresent = byUID || !i4set.contains(null);
i4set.remove(null);
try {
// list of tag names (not including Flags)
List<String> tags = Lists.newArrayList();
//just Flag objects, no need to convert Tag objects to ImapFlag here
Set<ImapFlag> i4flags = new HashSet<ImapFlag>(flagNames.size());
for (String name : flagNames) {
ImapFlag i4flag = i4folder.getFlagByName(name);
if (i4flag == null) {
tags.add(name);
//new tag for this folder
continue;
} else if (i4flag.mId > 0) {
tags.add(i4flag.mName);
} else {
i4flags.add(i4flag);
}
if (operation != StoreAction.REMOVE) {
if (i4flag.mId == Flag.ID_DELETED) {
if (!i4folder.getPath().isWritable(ACL.RIGHT_DELETE)) {
throw ServiceException.PERM_DENIED("you do not have permission to set the \\Deleted flag");
}
} else if (i4flag.mPermanent) {
if (!i4folder.getPath().isWritable(ACL.RIGHT_WRITE)) {
throw ServiceException.PERM_DENIED("you do not have permission to set the " + i4flag.mName + " flag");
}
}
}
}
// if we're doing a STORE FLAGS (i.e. replace), precompute the new set of flags for all the affected messages
int flags = Flag.BITMASK_UNREAD;
short sflags = 0;
if (operation == StoreAction.REPLACE) {
for (ImapFlag i4flag : i4flags) {
if (!i4flag.mPermanent) {
sflags = (byte) (i4flag.mPositive ? sflags | i4flag.mBitmask : sflags & ~i4flag.mBitmask);
} else {
flags = (int) (i4flag.mPositive ? flags | i4flag.mBitmask : flags & ~i4flag.mBitmask);
}
}
}
long checkpoint = System.currentTimeMillis();
int i = 0;
List<ImapMessage> i4list = new ArrayList<ImapMessage>(SUGGESTED_BATCH_SIZE);
List<Integer> idlist = new ArrayList<Integer>(SUGGESTED_BATCH_SIZE);
for (ImapMessage msg : i4set) {
// we're sending 'em off in batches of 100
i4list.add(msg);
idlist.add(msg.msgId);
if (++i % SUGGESTED_BATCH_SIZE != 0 && i != i4set.size()) {
continue;
}
mbox.lock.lock();
try {
if (modseq >= 0) {
MailItem[] items = mbox.getItemById(getContext(), idlist, MailItem.Type.UNKNOWN);
for (int idx = items.length - 1; idx >= 0; idx--) {
ImapMessage i4msg = i4list.get(idx);
if (i4msg.getModseq(items[idx]) > modseq) {
modifyConflicts.add(i4msg);
i4list.remove(idx);
idlist.remove(idx);
allPresent = false;
}
}
}
try {
// if it was a STORE [+-]?FLAGS.SILENT, temporarily disable notifications
if (silent && !modseqEnabled) {
i4folder.disableNotifications();
}
if (operation == StoreAction.REPLACE) {
// replace real tags and flags on all messages
mbox.setTags(getContext(), ArrayUtil.toIntArray(idlist), MailItem.Type.UNKNOWN, flags, tags.toArray(new String[tags.size()]), null);
// replace session tags on all messages
for (ImapMessage i4msg : i4list) {
i4msg.setSessionFlags(sflags, i4folder);
}
} else {
for (ImapFlag i4flag : i4flags) {
boolean add = operation == StoreAction.ADD ^ !i4flag.mPositive;
if (i4flag.mPermanent) {
// real Flag (not a Tag); do a batch update to the DB
if ((i4flag.mBitmask & Flag.BITMASK_DELETED) > 0) {
ZimbraLog.imap.info("IMAP client has flagged the item with id %d to be Deleted altertag", msg.msgId);
}
mbox.alterTag(getContext(), ArrayUtil.toIntArray(idlist), MailItem.Type.UNKNOWN, i4flag.mName, add, null);
} else {
// session tag; update one-by-one in memory only
for (ImapMessage i4msg : i4list) {
i4msg.setSessionFlags((short) (add ? i4msg.sflags | i4flag.mBitmask : i4msg.sflags & ~i4flag.mBitmask), i4folder);
}
}
}
boolean add = operation == StoreAction.ADD;
//add (or remove) Tags
for (String tagName : tags) {
mbox.alterTag(getContext(), ArrayUtil.toIntArray(idlist), MailItem.Type.UNKNOWN, tagName, add, null);
}
}
} finally {
// if it was a STORE [+-]?FLAGS.SILENT, reenable notifications
i4folder.enableNotifications();
}
} finally {
mbox.lock.release();
}
if (!silent || modseqEnabled) {
for (ImapMessage i4msg : i4list) {
ImapFolder.DirtyMessage dirty = i4folder.undirtyMessage(i4msg);
if (silent && (dirty == null || dirty.modseq <= 0)) {
continue;
}
StringBuilder ntfn = new StringBuilder();
boolean empty = true;
ntfn.append(i4msg.sequence).append(" FETCH (");
if (!silent) {
ntfn.append(i4msg.getFlags(i4folder));
empty = false;
}
// caused by a UID command..."
if (byUID) {
ntfn.append(empty ? "" : " ").append("UID ").append(i4msg.imapUid);
empty = false;
}
if (dirty != null && dirty.modseq > 0 && modseqEnabled) {
ntfn.append(empty ? "" : " ").append("MODSEQ (").append(dirty.modseq).append(')');
empty = false;
}
sendUntagged(ntfn.append(')').toString());
}
} else {
// send a gratuitous untagged response to keep pissy clients from closing the socket from inactivity
long now = System.currentTimeMillis();
if (now - checkpoint > MAXIMUM_IDLE_PROCESSING_MILLIS) {
sendIdleUntagged();
checkpoint = now;
}
}
i4list.clear();
idlist.clear();
}
} catch (ServiceException e) {
deleteTags(newTags);
if (e.getCode().equals(MailServiceException.INVALID_NAME)) {
ZimbraLog.imap.info("%s failed: %s", command, e.getMessage());
} else {
ZimbraLog.imap.warn("%s failed", command, e);
}
sendNO(tag, command + " failed");
return canContinue(e);
}
boolean hadConflicts = modifyConflicts != null && !modifyConflicts.isEmpty();
String conflicts = hadConflicts ? " [MODIFIED " + ImapFolder.encodeSubsequence(modifyConflicts, byUID) + ']' : "";
sendNotifications(byUID, false);
// a FETCH response for the non-expunged messages along with a tagged NO."
if (silent || allPresent) {
sendOK(tag, command + conflicts + " completed");
} else {
sendNO(tag, command + conflicts + " completed");
}
return true;
}
Aggregations