Modified: james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/StoreProcessor.java URL: http://svn.apache.org/viewvc/james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/StoreProcessor.java?rev=1146234&r1=1146233&r2=1146234&view=diff ============================================================================== --- james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/StoreProcessor.java (original) +++ james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/StoreProcessor.java Wed Jul 13 19:03:43 2011 @@ -20,6 +20,8 @@ package org.apache.james.imap.processor; import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -32,6 +34,7 @@ import org.apache.james.imap.api.display import org.apache.james.imap.api.message.IdRange; import org.apache.james.imap.api.message.response.StatusResponse; import org.apache.james.imap.api.message.response.StatusResponseFactory; +import org.apache.james.imap.api.message.response.StatusResponse.ResponseCode; import org.apache.james.imap.api.process.ImapProcessor; import org.apache.james.imap.api.process.ImapSession; import org.apache.james.imap.api.process.SelectedMailbox; @@ -44,56 +47,115 @@ import org.apache.james.mailbox.MailboxS import org.apache.james.mailbox.MessageManager; import org.apache.james.mailbox.MessageManager.MetaData.FetchGroup; import org.apache.james.mailbox.MessageRange; +import org.apache.james.mailbox.MessageRange.Type; import org.apache.james.mailbox.MessageRangeException; import org.apache.james.mailbox.MessageResult; public class StoreProcessor extends AbstractMailboxProcessor<StoreRequest> { + /** + * The {@link ImapCommand} which should be used for the response if some CONDSTORE option is used + */ + private final static ImapCommand CONDSTORE_COMMAND = ImapCommand.selectedStateCommand("Conditional STORE"); + public StoreProcessor(final ImapProcessor next, final MailboxManager mailboxManager, final StatusResponseFactory factory) { super(StoreRequest.class, next, mailboxManager, factory); } + /* + * (non-Javadoc) + * @see org.apache.james.imap.processor.AbstractMailboxProcessor#doProcess(org.apache.james.imap.api.message.request.ImapRequest, org.apache.james.imap.api.process.ImapSession, java.lang.String, org.apache.james.imap.api.ImapCommand, org.apache.james.imap.api.process.ImapProcessor.Responder) + */ protected void doProcess(StoreRequest request, ImapSession session, String tag, ImapCommand command, Responder responder) { final IdRange[] idSet = request.getIdSet(); final boolean useUids = request.isUseUids(); final long unchangedSince = request.getUnchangedSince(); - + ImapCommand imapCommand = command; + try { final MessageManager mailbox = getSelectedMailbox(session); final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session); - - if (unchangedSince != -1 && mailbox.isModSeqPermanent(mailboxSession) == false ) { - // Check if the mailbox did not support modsequences. If so return a tagged bad response. - // See RFC4551 3.1.2. NOMODSEQ Response Code - taggedBad(command, tag, responder, HumanReadableText.NO_MOD_SEQ); - return; - } - List<Long> failedUids = new ArrayList<Long>(); - + final Flags flags = request.getFlags(); + + if (unchangedSince != -1) { + if (mailbox.isModSeqPermanent(mailboxSession) == false) { + // Check if the mailbox did not support modsequences. If so return a tagged bad response. + // See RFC4551 3.1.2. NOMODSEQ Response Code + taggedBad(command, tag, responder, HumanReadableText.NO_MOD_SEQ); + return; + } else if (unchangedSince == 0){ + Flags.Flag[] systemFlags = flags.getSystemFlags(); + if (systemFlags != null && systemFlags.length != 0) { + // we need to return all sequences as failed when using a UNCHANGEDSINCE 0 and the request specify a SYSTEM flags + // + // See RFC4551 3.2. STORE and UID STORE Command; + // + // Use of UNCHANGEDSINCE with a modification sequence of 0 always + // fails if the metadata item exists. A system flag MUST always be + // considered existent, whether it was set or not. + final StatusResponse response = getStatusResponseFactory().taggedOk(tag, command, HumanReadableText.FAILED, ResponseCode.condStore(idSet)); + responder.respond(response); + return; + } + } + + } + final List<Long> failed = new ArrayList<Long>(); + final List<String> userFlags = Arrays.asList(flags.getUserFlags()); for (int i = 0; i < idSet.length; i++) { final SelectedMailbox selected = session.getSelected(); MessageRange messageSet = messageRange(selected, idSet[i], useUids); if (messageSet != null) { if (unchangedSince != -1) { + // Ok we have a CONDSTORE option so use the CONDSTORE_COMMAND + imapCommand = CONDSTORE_COMMAND; + List<Long> uids = new ArrayList<Long>(); Iterator<MessageResult> results = mailbox.getMessages(messageSet, FetchGroupImpl.MINIMAL, mailboxSession); while(results.hasNext()) { MessageResult r = results.next(); long uid = r.getUid(); - if (r.getModSeq() <= unchangedSince) { + + boolean fail = false; + + // Check if UNCHANGEDSINCE 0 was used and the Message contains the request flag. + // In such cases we need to fail for this message. + // + // From RFC4551: + // Use of UNCHANGEDSINCE with a modification sequence of 0 always + // fails if the metadata item exists. A system flag MUST always be + // considered existent, whether it was set or not. + if (unchangedSince == 0) { + String[] uFlags = r.getFlags().getUserFlags(); + for (int a = 0; a < uFlags.length; a++) { + if (userFlags.contains(uFlags[a])) { + fail = true; + break; + } + } + } + + // Check if the mod-sequence of the message is <= the unchangedsince. + // + // See RFC4551 3.2. STORE and UID STORE Commands + if (!fail && r.getModSeq() <= unchangedSince) { uids.add(uid); } else { - failedUids.add(uid); + if (useUids) { + failed.add(uid); + } else { + failed.add((long) selected.msn(uid)); + } } } List<MessageRange> mRanges = MessageRange.toRanges(uids); for (int a = 0 ; a < mRanges.size(); a++) { - setFlags(request, mailboxSession, mailbox, mRanges.get(a), selected, tag, command, responder); + setFlags(request, mailboxSession, mailbox, mRanges.get(a), selected, tag, imapCommand, responder); } } else { - setFlags(request, mailboxSession, mailbox, messageSet, selected, tag, command, responder); + setFlags(request, mailboxSession, mailbox, messageSet, selected, tag, imapCommand, responder); } } @@ -102,22 +164,52 @@ public class StoreProcessor extends Abst } final boolean omitExpunged = (!useUids); unsolicitedResponses(session, responder, omitExpunged, useUids); - if (failedUids.isEmpty()) { - okComplete(command, tag, responder); + + // check if we had some failed uids which didn't pass the UNCHANGEDSINCE filter + if (failed.isEmpty()) { + okComplete(imapCommand, tag, responder); } else { - // TODO: Fix me! - final StatusResponse response = getStatusResponseFactory().taggedOk(tag, command, HumanReadableText.COMPLETED); + // Convert the MessageRanges to an array of IdRange. + // TODO: Maybe this should get moved in an util class + List<MessageRange> ranges = MessageRange.toRanges(failed); + IdRange[] idRanges = new IdRange[ranges.size()]; + for (int i = 0 ; i < ranges.size(); i++) { + MessageRange r = ranges.get(i); + if (r.getType() == Type.ONE) { + idRanges[i] = new IdRange(r.getUidFrom()); + } else { + idRanges[i] = new IdRange(r.getUidFrom(), r.getUidTo()); + } + } + // we need to return the failed sequences + // + // See RFC4551 3.2. STORE and UID STORE Commands + final StatusResponse response = getStatusResponseFactory().taggedOk(tag, command, HumanReadableText.FAILED, ResponseCode.condStore(idRanges)); responder.respond(response); + } } catch (MessageRangeException e) { session.getLog().debug("Store failed", e); - taggedBad(command, tag, responder, HumanReadableText.INVALID_MESSAGESET); + taggedBad(imapCommand, tag, responder, HumanReadableText.INVALID_MESSAGESET); } catch (MailboxException e) { session.getLog().debug("Store failed", e); - no(command, tag, responder, HumanReadableText.SAVE_FAILED); + no(imapCommand, tag, responder, HumanReadableText.SAVE_FAILED); } } + /** + * Set the flags for given messages + * + * @param request + * @param mailboxSession + * @param mailbox + * @param messageSet + * @param selected + * @param tag + * @param command + * @param responder + * @throws MailboxException + */ private void setFlags(StoreRequest request, MailboxSession mailboxSession, MessageManager mailbox, MessageRange messageSet, SelectedMailbox selected, String tag, ImapCommand command, Responder responder) throws MailboxException { final Flags flags = request.getFlags(); @@ -125,7 +217,7 @@ public class StoreProcessor extends Abst final boolean silent = request.isSilent(); final boolean isSignedPlus = request.isSignedPlus(); final boolean isSignedMinus = request.isSignedMinus(); - + final long unchangedSince = request.getUnchangedSince(); final boolean replace; final boolean value; if (isSignedMinus) { @@ -149,8 +241,19 @@ public class StoreProcessor extends Abst selected.resetNewApplicableFlags(); } - if (!silent) { - + if (!silent || unchangedSince != -1) { + final Map<Long, Long> modSeqs = new HashMap<Long, Long>(); + + // Check if we need to also send the the mod-sequences back to the client + if (unchangedSince != -1) { + Iterator<MessageResult> results = mailbox.getMessages(messageSet, FetchGroupImpl.MINIMAL, mailboxSession); + while(results.hasNext()) { + MessageResult r = results.next(); + // Store the modseq for the uid for later usage in the response + modSeqs.put(r.getUid(),r.getModSeq()); + } + } + for (Map.Entry<Long, Flags> entry : flagsByUid.entrySet()) { final long uid = entry.getKey(); final int msn = selected.msn(uid); @@ -165,14 +268,29 @@ public class StoreProcessor extends Abst } else { resultUid = null; } - - + if (selected.isRecent(uid)) { resultFlags.add(Flags.Flag.RECENT); } - final FetchResponse response = new FetchResponse(msn, resultFlags, resultUid, null, null, null, null, null, null); + + final FetchResponse response; + // For more informations related to the FETCH response see + // + // RFC4551 3.2. STORE and UID STORE Commands + if (silent && unchangedSince != -1) { + // We need to return an FETCH response which contains the mod-sequence of the message even if FLAGS.SILENT was used + response = new FetchResponse(msn, null, resultUid, modSeqs.get(resultUid), null, null, null, null, null, null); + } else if (!silent && unchangedSince != -1){ + // + // Use a FETCH response which contains the mod-sequence and the flags + response = new FetchResponse(msn, resultFlags, resultUid, modSeqs.get(resultUid), null, null, null, null, null, null); + } else { + // Use a FETCH response which only contains the flags as no CONDSTORE was used + response = new FetchResponse(msn, resultFlags, resultUid, null, null, null, null, null, null, null); + } responder.respond(response); + } } }
Modified: james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java URL: http://svn.apache.org/viewvc/james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java?rev=1146234&r1=1146233&r2=1146234&view=diff ============================================================================== --- james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java (original) +++ james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/base/SelectedMailboxImpl.java Wed Jul 13 19:03:43 2011 @@ -48,7 +48,9 @@ public class SelectedMailboxImpl impleme private boolean recentUidRemoved; - public SelectedMailboxImpl(final MailboxManager mailboxManager, final Iterator<Long> uids, final Flags applicableFlags, final ImapSession session, final MailboxPath path) throws MailboxException { + private final boolean condstore; + + public SelectedMailboxImpl(final MailboxManager mailboxManager, final Iterator<Long> uids, final Flags applicableFlags, final ImapSession session, final MailboxPath path, final boolean condstore) throws MailboxException { recentUids = new TreeSet<Long>(); recentUidRemoved = false; MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session); @@ -58,6 +60,7 @@ public class SelectedMailboxImpl impleme mailboxManager.addListener(path, events, mailboxSession); converter = new UidToMsnConverter(session, uids); mailboxManager.addListener(path, converter, mailboxSession); + this.condstore = condstore; } /** @@ -284,4 +287,9 @@ public class SelectedMailboxImpl impleme public void resetNewApplicableFlags() { events.resetNewApplicableFlags(); } + + @Override + public boolean getCondstore() { + return condstore; + } } Modified: james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/fetch/FetchResponseBuilder.java URL: http://svn.apache.org/viewvc/james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/fetch/FetchResponseBuilder.java?rev=1146234&r1=1146233&r2=1146234&view=diff ============================================================================== --- james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/fetch/FetchResponseBuilder.java (original) +++ james/imap/trunk/processor/src/main/java/org/apache/james/imap/processor/fetch/FetchResponseBuilder.java Wed Jul 13 19:03:43 2011 @@ -59,6 +59,8 @@ public final class FetchResponseBuilder private Date internalDate; private Long size; + + private Long modSeq; private List<FetchResponse.BodyElement> elements; @@ -82,18 +84,24 @@ public final class FetchResponseBuilder body = null; bodystructure = null; elements = null; + modSeq = null; } public void setUid(long uid) { this.uid = uid; } + private void setModSeq(long modSeq) { + this.modSeq = modSeq; + } + + public void setFlags(Flags flags) { this.flags = flags; } public FetchResponse build() { - final FetchResponse result = new FetchResponse(msn, flags, uid, internalDate, size, envelope, body, bodystructure, elements); + final FetchResponse result = new FetchResponse(msn, flags, uid, modSeq, internalDate, size, envelope, body, bodystructure, elements); return result; } @@ -171,6 +179,18 @@ public final class FetchResponseBuilder this.elements.add(element); } } + + if (fetch.isModSeq()) { + long changedSince = fetch.getChangedSince(); + if (changedSince != -1) { + // check if the modsequence if higher then the one specified by the CHANGEDSINCE option + if (changedSince < result.getModSeq()) { + setModSeq(result.getModSeq()); + } + } else { + setModSeq(result.getModSeq()); + } + } return build(); } Modified: james/imap/trunk/processor/src/test/java/org/apache/james/imap/processor/SearchProcessorTest.java URL: http://svn.apache.org/viewvc/james/imap/trunk/processor/src/test/java/org/apache/james/imap/processor/SearchProcessorTest.java?rev=1146234&r1=1146233&r2=1146234&view=diff ============================================================================== --- james/imap/trunk/processor/src/test/java/org/apache/james/imap/processor/SearchProcessorTest.java (original) +++ james/imap/trunk/processor/src/test/java/org/apache/james/imap/processor/SearchProcessorTest.java Wed Jul 13 19:03:43 2011 @@ -485,13 +485,15 @@ public class SearchProcessorTest { private void check(final SearchKey key, final SearchQuery query) throws Exception { mockery.checking(new Expectations() {{ allowing(session).getAttribute( + with(equal(SearchProcessor.SEARCH_MODSEQ))); will(returnValue(null)); + allowing(session).getAttribute( with(equal(ImapSessionUtils.MAILBOX_SESSION_ATTRIBUTE_SESSION_KEY))); will(returnValue((MailboxSession) mailboxSession)); allowing(session).getLog(); will(returnValue(logger)); oneOf(mailbox).search( with(equal(query)), with(equal(mailboxSession)));will( returnValue(new ArrayList<Long>().iterator())); - oneOf(responder).respond(with(equal(new SearchResponse(EMPTY)))); + oneOf(responder).respond(with(equal(new SearchResponse(EMPTY, null)))); allowing(selectedMailbox).getApplicableFlags(); will(returnValue(new Flags())); allowing(selectedMailbox).hasNewApplicableFlags(); will(returnValue(false)); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
