Index: api/pom.xml =================================================================== --- api/pom.xml (revision 1065029) +++ api/pom.xml (working copy) @@ -43,6 +43,10 @@ commons-logging commons-logging + + commons-lang + commons-lang + junit junit-dep Index: api/src/main/java/org/apache/james/imap/api/message/IdRange.java =================================================================== --- api/src/main/java/org/apache/james/imap/api/message/IdRange.java (revision 1065029) +++ api/src/main/java/org/apache/james/imap/api/message/IdRange.java (working copy) @@ -113,6 +113,13 @@ return retValue; } + public String getFormattedString() { + if(this._lowVal == this._highVal) + return Long.toString(this._lowVal); + else + return this._lowVal+":"+this._highVal; + } + /** * Utility method which will copy the given {@link List} and try to merge the * {@link IdRange} in the copy before return it. Index: api/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java =================================================================== --- api/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java (revision 1065029) +++ api/src/main/java/org/apache/james/imap/api/message/response/StatusResponse.java (working copy) @@ -19,13 +19,18 @@ package org.apache.james.imap.api.message.response; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.List; import javax.mail.Flags; +import org.apache.commons.lang.StringUtils; import org.apache.james.imap.api.ImapCommand; import org.apache.james.imap.api.display.HumanReadableText; +import org.apache.james.imap.api.message.IdRange; import org.apache.james.imap.api.message.MessageFlags; /** @@ -127,6 +132,29 @@ private static final ResponseCode TRYCREATE = new ResponseCode( "TRYCREATE"); + /** RFC4315 APPENDUID response code */ + public static final ResponseCode appendUid(long uidValidity, IdRange[] uids) { + String uidParam = formatRanges(uids); + return new ResponseCode("APPENDUID", Arrays.asList(uidParam), uidValidity, false); + } + + /** RFC4315 COPYUID response code */ + public static final ResponseCode copyUid(long uidValidity, IdRange[] sourceRanges, IdRange[] targetRanges) { + String source = formatRanges(sourceRanges); + String target = formatRanges(targetRanges); + + return new ResponseCode("COPYUID", Arrays.asList(new String[] {source, target}), uidValidity, false); + } + + private static String formatRanges(IdRange[] ranges) { + if(ranges == null || ranges.length == 0) + return "*"; + List textRanges = new ArrayList(ranges.length); + for(IdRange range : ranges) + textRanges.add(range.getFormattedString()); + return StringUtils.join(textRanges, ","); + } + /** * Creates a RFC2060 ALERT response code. * @@ -251,24 +279,27 @@ private final Collection parameters; private final long number; + + private final boolean useParens; @SuppressWarnings ("unchecked") private ResponseCode(final String code) { - this(code, Collections.EMPTY_LIST, 0); + this(code, Collections.EMPTY_LIST, 0, true); } @SuppressWarnings ("unchecked") private ResponseCode(final String code, final long number) { - this(code, Collections.EMPTY_LIST, number); + this(code, Collections.EMPTY_LIST, number, true); } private ResponseCode(final String code, final Collection parameters) { - this(code, parameters, 0); + this(code, parameters, 0, true); } private ResponseCode(final String code, final Collection parameters, - final long number) { + final long number, final boolean useParens) { super(); + this.useParens = useParens; this.code = code; this.parameters = parameters; this.number = number; @@ -287,6 +318,10 @@ return number; } + public final boolean useParens() { + return useParens; + } + /** * Gets parameters for this code. * Index: message/src/main/java/org/apache/james/imap/encode/base/ImapResponseComposerImpl.java =================================================================== --- message/src/main/java/org/apache/james/imap/encode/base/ImapResponseComposerImpl.java (revision 1065029) +++ message/src/main/java/org/apache/james/imap/encode/base/ImapResponseComposerImpl.java (working copy) @@ -267,7 +267,7 @@ * java.lang.String, Collection, long, java.lang.String) */ public void statusResponse(String tag, ImapCommand command, String type, - String responseCode, Collection parameters, long number, String text) + String responseCode, Collection parameters, boolean useParens, long number, String text) throws IOException { if (tag == null) { untagged(); @@ -278,17 +278,19 @@ if (responseCode != null) { openSquareBracket(); message(responseCode); + if (number > 0) { + message(number); + } if (parameters != null && !parameters.isEmpty()) { - openParen(); + if(useParens) + openParen(); for (Iterator it = parameters.iterator(); it.hasNext();) { final String parameter = it.next(); message(parameter); } - closeParen(); + if(useParens) + closeParen(); } - if (number > 0) { - message(number); - } closeSquareBracket(); } if (command != null) { Index: message/src/main/java/org/apache/james/imap/encode/ImapResponseComposer.java =================================================================== --- message/src/main/java/org/apache/james/imap/encode/ImapResponseComposer.java (revision 1065029) +++ message/src/main/java/org/apache/james/imap/encode/ImapResponseComposer.java (working copy) @@ -201,7 +201,7 @@ public abstract void statusResponse(String tag, ImapCommand command, String type, String responseCode, Collection parameters, - long number, String text) throws IOException; + boolean useParens, long number, String text) throws IOException; public abstract void statusResponse(Long messages, Long recent, Long uidNext, Long uidValidity, Long unseen, String mailboxName) Index: message/src/main/java/org/apache/james/imap/encode/StatusResponseEncoder.java =================================================================== --- message/src/main/java/org/apache/james/imap/encode/StatusResponseEncoder.java (revision 1065029) +++ message/src/main/java/org/apache/james/imap/encode/StatusResponseEncoder.java (working copy) @@ -57,14 +57,17 @@ final String text = asString(textKey, session); final Collection parameters; final long number; + final boolean useParens; if (responseCode == null) { parameters = null; number = 0; + useParens = false; } else { parameters = responseCode.getParameters(); number = responseCode.getNumber(); + useParens = responseCode.useParens(); } - composer.statusResponse(tag, command, type, code, parameters, number, + composer.statusResponse(tag, command, type, code, parameters, useParens, number, text); } Index: message/src/test/java/org/apache/james/imap/encode/AbstractTestImapResponseComposer.java =================================================================== --- message/src/test/java/org/apache/james/imap/encode/AbstractTestImapResponseComposer.java (revision 1065029) +++ message/src/test/java/org/apache/james/imap/encode/AbstractTestImapResponseComposer.java (working copy) @@ -155,11 +155,11 @@ @Test public void testShouldEncodeUnparameterisedStatus() throws Exception { checkStatusResponseEncode("A1 NO [ALERT] APPEND failed\r\n", "A1", - command("APPEND"), "NO", "ALERT", new ArrayList(), 0, + command("APPEND"), "NO", "ALERT", new ArrayList(), true, 0, "failed"); checkStatusResponseEncode("A1 BAD [TRYCREATE] SELECT whatever\r\n", "A1", command("SELECT"), "BAD", "TRYCREATE", - new ArrayList(), 0, "whatever"); + new ArrayList(), true, 0, "whatever"); } @Test @@ -170,15 +170,31 @@ parameters.add("THREE"); checkStatusResponseEncode( "A1 NO [BADCHARSET (ONE TWO THREE)] APPEND failed\r\n", "A1", - command("APPEND"), "NO", "BADCHARSET", parameters, 0, "failed"); + command("APPEND"), "NO", "BADCHARSET", parameters, true, 0, "failed"); } @Test public void testShouldEncodeNumberParameterStatus() throws Exception { checkStatusResponseEncode("A1 NO [UIDNEXT 10] APPEND failed\r\n", "A1", - command("APPEND"), "NO", "UIDNEXT", null, 10, "failed"); + command("APPEND"), "NO", "UIDNEXT", null, true, 10, "failed"); } + @Test + public void testShouldEncodeNumberAndListParameterStatus() throws Exception { + Collection parameters = new ArrayList(); + parameters.add("1:2"); + parameters.add("3:4"); + + checkStatusResponseEncode("A1 OK [COPYUID 10 1:2 3:4] COPY completed\r\n", "A1", + command("COPY"), "OK", "COPYUID", parameters, false, 10, "completed"); + + parameters.clear(); + parameters.add("3"); + + checkStatusResponseEncode("A1 OK [APPENDUID 10 3] APPEND completed\r\n", "A1", + command("APPEND"), "OK", "APPENDUID", parameters, false, 10, "completed"); + } + private void checkFlagsEncode(String expected, Flags flags) throws Exception { StringBuffer buffer = new StringBuffer(); @@ -243,14 +259,14 @@ protected abstract byte[] encodeStatusResponse(String tag, ImapCommand command, String type, String responseCode, - Collection parameters, int number, String text) throws Exception; + Collection parameters, boolean useParens, int number, String text) throws Exception; private void checkStatusResponseEncode(String expected, String tag, ImapCommand command, String type, String responseCode, - Collection parameters, int number, String text) throws Exception { + Collection parameters, boolean useParens, int number, String text) throws Exception { StringBuffer buffer = new StringBuffer(); byte[] output = encodeStatusResponse(tag, command, type, responseCode, - parameters, number, text); + parameters, useParens, number, text); for (int i = 0; i < output.length; i++) { buffer.append((char) output[i]); } Index: message/src/test/java/org/apache/james/imap/encode/base/ImapResponseComposerImplTest.java =================================================================== --- message/src/test/java/org/apache/james/imap/encode/base/ImapResponseComposerImplTest.java (revision 1065029) +++ message/src/test/java/org/apache/james/imap/encode/base/ImapResponseComposerImplTest.java (working copy) @@ -71,10 +71,10 @@ } protected byte[] encodeStatusResponse(String tag, ImapCommand command, - String type, String responseCode, Collection parameters, - int number, String text) throws Exception { + String type, String responseCode, Collection parameters, + boolean useParens, int number, String text) throws Exception { composer.statusResponse(tag, command, type, responseCode, parameters, - number, text); + useParens, number, text); return writer.getBytes(); } Index: processor/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java =================================================================== --- processor/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java (revision 1065029) +++ processor/src/main/java/org/apache/james/imap/processor/AbstractMailboxProcessor.java (working copy) @@ -31,6 +31,7 @@ import org.apache.james.imap.api.message.response.ImapResponseMessage; 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; @@ -241,6 +242,13 @@ HumanReadableText.COMPLETED); responder.respond(response); } + + protected void okComplete(final ImapCommand command, final String tag, + final ResponseCode code, final ImapProcessor.Responder responder) { + final StatusResponse response = factory.taggedOk(tag, command, + HumanReadableText.COMPLETED, code); + responder.respond(response); + } protected void no(final ImapCommand command, final String tag, final ImapProcessor.Responder responder, Index: processor/src/main/java/org/apache/james/imap/processor/AppendProcessor.java =================================================================== --- processor/src/main/java/org/apache/james/imap/processor/AppendProcessor.java (revision 1065029) +++ processor/src/main/java/org/apache/james/imap/processor/AppendProcessor.java (working copy) @@ -29,8 +29,10 @@ import org.apache.james.imap.api.ImapCommand; import org.apache.james.imap.api.ImapSessionUtils; import org.apache.james.imap.api.display.HumanReadableText; +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; @@ -41,6 +43,7 @@ import org.apache.james.mailbox.MailboxPath; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.MessageManager.MetaData.FetchGroup; public class AppendProcessor extends AbstractMailboxProcessor { @@ -69,7 +72,7 @@ command, mailbox, responder, mailboxPath); } catch (MailboxNotFoundException e) { // consume message on exception - cosume(messageIn); + consume(messageIn); // Indicates that the mailbox does not exist // So TRY CREATE @@ -77,7 +80,7 @@ } catch (MailboxException e) { // consume message on exception - cosume(messageIn); + consume(messageIn); // Some other issue no(command, tag, responder, HumanReadableText.GENERIC_FAILURE_DURING_PROCESSING); @@ -86,7 +89,7 @@ } - private void cosume(InputStream in) { + private void consume(InputStream in) { try { while(in.read() != -1); } catch (IOException e1) { @@ -121,6 +124,7 @@ try { final MailboxSession mailboxSession = ImapSessionUtils.getMailboxSession(session); final SelectedMailbox selectedMailbox = session.getSelected(); + final MailboxManager mailboxManager = getMailboxManager(); final boolean isSelectedMailbox = selectedMailbox != null && selectedMailbox.getPath().equals(mailboxPath); final long uid = mailbox.appendMessage(message, datetime, mailboxSession, @@ -128,8 +132,14 @@ if (isSelectedMailbox) { selectedMailbox.addRecent(uid); } + + // get folder UIDVALIDITY + Long uidValidity = mailboxManager.getMailbox(mailboxPath, mailboxSession).getMetaData(false, mailboxSession, FetchGroup.NO_UNSEEN).getUidValidity(); + unsolicitedResponses(session, responder, false); - okComplete(command, tag, responder); + + // in case of MULTIAPPEND support we will push more then one UID here + okComplete(command, tag, ResponseCode.appendUid(uidValidity, new IdRange[] { new IdRange(uid) }), responder); } catch (MailboxNotFoundException e) { // Indicates that the mailbox does not exist // So TRY CREATE Index: processor/src/main/java/org/apache/james/imap/processor/CopyProcessor.java =================================================================== --- processor/src/main/java/org/apache/james/imap/processor/CopyProcessor.java (revision 1065029) +++ processor/src/main/java/org/apache/james/imap/processor/CopyProcessor.java (working copy) @@ -19,10 +19,15 @@ package org.apache.james.imap.processor; +import java.util.ArrayList; +import java.util.List; import org.apache.james.imap.api.ImapCommand; import org.apache.james.imap.api.ImapSessionUtils; import org.apache.james.imap.api.display.HumanReadableText; import org.apache.james.imap.api.message.IdRange; +import org.apache.james.imap.api.message.request.ImapRequest; +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.message.response.StatusResponseFactory; import org.apache.james.imap.api.process.ImapProcessor; @@ -35,6 +40,7 @@ import org.apache.james.mailbox.MailboxPath; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.MessageRange; +import org.apache.james.mailbox.MessageManager.MetaData.FetchGroup; public class CopyProcessor extends AbstractMailboxProcessor { @@ -62,15 +68,23 @@ no(command, tag, responder, HumanReadableText.FAILURE_NO_SUCH_MAILBOX, ResponseCode.tryCreate()); } else { + List resultRanges=new ArrayList(); for (int i = 0; i < idSet.length; i++) { - MessageRange messageSet = messageRange(currentMailbox, idSet[i], useUids); - mailboxManager.copyMessages(messageSet, currentMailbox.getPath(), - targetMailbox, mailboxSession); + List copiedUids = mailboxManager.copyMessages(messageSet, currentMailbox.getPath(), targetMailbox, mailboxSession); + + for (MessageRange mr : copiedUids) { + resultRanges.add(new IdRange(mr.getUidFrom(), mr.getUidTo())); + } } + IdRange[] resultUids = IdRange.mergeRanges(resultRanges).toArray(new IdRange[0]); + + // get folder UIDVALIDITY + Long uidValidity = mailboxManager.getMailbox(targetMailbox, mailboxSession).getMetaData(false, mailboxSession, FetchGroup.NO_UNSEEN).getUidValidity(); + unsolicitedResponses(session, responder, useUids); - okComplete(command, tag, responder); + okComplete(command, tag, ResponseCode.copyUid(uidValidity, idSet, resultUids), responder); } } catch (MessageRangeException e) { taggedBad(command, tag, responder, HumanReadableText.INVALID_MESSAGESET);