Index: apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java =================================================================== --- apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java (revision 1417296) +++ apache-james-mailbox/api/src/main/java/org/apache/james/mailbox/MailboxManager.java (working copy) @@ -145,6 +145,23 @@ List copyMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; /** + * Move the given {@link MessageRange} from one Mailbox to the other. + * + * Be aware that the moved Messages MUST get the \RECENT flag set! + * + * @param set + * messages to move + * @param from + * name of the source mailbox + * @param to + * name of the destination mailbox + * @param session + * MailboxSession, not null + * @return a list of MessageRange - uids assigned to moved messages + */ + List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException; + + /** * Searches for mailboxes matching the given query. * * @param expression Index: apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java =================================================================== --- apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java (revision 1417296) +++ apache-james-mailbox/hbase/src/main/java/org/apache/james/mailbox/hbase/mail/HBaseMessageMapper.java (working copy) @@ -555,6 +555,16 @@ /* * (non-Javadoc) + * @see org.apache.james.mailbox.store.mail.MessageMapper#copy(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + @Override + public MessageMetaData move(Mailbox mailbox, Message original) throws MailboxException { + //TODO implement if possible + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) * @see org.apache.james.mailbox.store.mail.MessageMapper#getLastUid(org.apache.james.mailbox.store.mail.model.Mailbox) */ @Override Index: apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java =================================================================== --- apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java (revision 1417296) +++ apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/AbstractMessageMapper.java (working copy) @@ -147,8 +147,23 @@ } + /** + * @see org.apache.james.mailbox.store.mail.MessageMapper#move(org.apache.james.mailbox.store.mail.model.Mailbox, org.apache.james.mailbox.store.mail.model.Message) + */ + public MessageMetaData move(final Mailbox mailbox, final Message original) throws MailboxException { + long uid = uidProvider.nextUid(mailboxSession, mailbox); + long modSeq = -1; + if (modSeqProvider != null) { + modSeq = modSeqProvider.nextModSeq(mailboxSession, mailbox); + } + final MessageMetaData metaData = move(mailbox, uid, modSeq, original); + return metaData; + } + + + /** * Save the {@link Message} for the given {@link Mailbox} and return the {@link MessageMetaData} * @@ -172,4 +187,19 @@ */ protected abstract MessageMetaData copy(Mailbox mailbox, long uid, long modSeq, Message original) throws MailboxException; + /** + * Move the Message to the Mailbox, using the given uid and modSeq for the new Message
+ * This default implementation has to be overridden + * + * @param mailbox + * @param uid + * @param modSeq + * @param original + * @return metaData + * @throws MailboxException + */ + protected MessageMetaData move(Mailbox mailbox, long uid, long modSeq, Message original) throws MailboxException{ + throw new MailboxException("MOVE operation not supported by " + getClass().getName()); } + +} Index: apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java =================================================================== --- apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java (revision 1417296) +++ apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/MessageMapper.java (working copy) @@ -152,7 +152,17 @@ */ MessageMetaData copy(Mailbox mailbox,Message original) throws MailboxException; + /** + * Move the given {@link Message} to a new mailbox and return the uid of the moved. Be aware that the given uid is just a suggestion for the uid of the moved + * message. Implementation may choose to use a different one, so only depend on the returned uid! + * + * @param mailbox the Mailbox to move to + * @param original the original to move + * @throws StorageException + */ + MessageMetaData move(Mailbox mailbox,Message original) throws MailboxException; + /** * Return the last uid which were used for storing a Message in the {@link Mailbox} * Index: apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java =================================================================== --- apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java (revision 1417296) +++ apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMailboxManager.java (working copy) @@ -85,6 +85,8 @@ private int copyBatchSize = 0; + private int moveBatchSize = 0; + private MailboxPathLocker locker; private MessageSearchIndex index; @@ -114,6 +116,10 @@ this.copyBatchSize = copyBatchSize; } + public void setMoveBatchSize(int moveBatchSize) { + this.moveBatchSize = moveBatchSize; + } + public void setFetchBatchSize(int fetchBatchSize) { this.fetchBatchSize = fetchBatchSize; } @@ -486,6 +492,26 @@ } /** + * @see org.apache.james.mailbox.MailboxManager#moveMessages(MessageRange, MailboxPath, MailboxPath, MailboxSession) + */ + @SuppressWarnings("unchecked") + public List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException { + StoreMessageManager toMailbox = (StoreMessageManager) getMailbox(to, session); + StoreMessageManager fromMailbox = (StoreMessageManager) getMailbox(from, session); + + if (moveBatchSize > 0) { + List movedRanges = new ArrayList(); + Iterator ranges = set.split(moveBatchSize).iterator(); + while(ranges.hasNext()) { + movedRanges.addAll(fromMailbox.copyTo(ranges.next(), toMailbox, session)); + } + return movedRanges; + } else { + return fromMailbox.moveTo(set, toMailbox, session); + } + } + + /** * @see org.apache.james.mailbox.MailboxManager#search(org.apache.james.mailbox.model.MailboxQuery, org.apache.james.mailbox.MailboxSession) */ public List search(final MailboxQuery mailboxExpression, MailboxSession session) Index: apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java =================================================================== --- apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java (revision 1417296) +++ apache-james-mailbox/store/src/main/java/org/apache/james/mailbox/store/StoreMessageManager.java (working copy) @@ -574,6 +574,34 @@ }, true); } + /** + * Move the {@link MessageRange} to the {@link StoreMessageManager} + * + * @param set + * @param toMailbox + * @param session + * @throws MailboxException + */ + public List moveTo(final MessageRange set, final StoreMessageManager toMailbox, final MailboxSession session) throws MailboxException { + if (!isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(getMailboxEntity()), session.getPathDelimiter()); + } + if (!toMailbox.isWriteable(session)) { + throw new ReadOnlyException(new StoreMailboxPath(toMailbox.getMailboxEntity()), session.getPathDelimiter()); + } + + //TODO lock the from mailbox too, in a non-deadlocking manner - how? + return locker.executeWithLock(session, new StoreMailboxPath(toMailbox.getMailboxEntity()), new MailboxPathLocker.LockAwareExecution>() { + + @Override + public List execute() throws MailboxException { + SortedMap movedUids = move(set, toMailbox, session); + dispatcher.added(session, movedUids, toMailbox.getMailboxEntity()); + return MessageRange.toRanges(new ArrayList(movedUids.keySet())); + } + }, true); + } + protected MessageMetaData appendMessageToStore(final Message message, MailboxSession session) throws MailboxException { final MessageMapper mapper = mapperFactory.getMessageMapper(session); return mapperFactory.getMessageMapper(session).execute(new Mapper.Transaction() { @@ -679,6 +707,26 @@ return copiedRows.iterator(); } + private Iterator move(Iterator> originalRows, + MailboxSession session) throws MailboxException { + final List movedRows = new ArrayList(); + final MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + while (originalRows.hasNext()) { + final Message originalMessage = originalRows.next(); + MessageMetaData data = messageMapper.execute(new Mapper.Transaction() { + public MessageMetaData run() throws MailboxException { + return messageMapper.move(getMailboxEntity(), originalMessage); + + } + + }); + movedRows.add(data); + } + return movedRows.iterator(); + } + + /** * @see org.apache.james.mailbox.store.AbstractStoreMessageManager#copy(org.apache.james.mailbox.model.MessageRange, * org.apache.james.mailbox.store.AbstractStoreMessageManager, @@ -698,6 +746,22 @@ return copiedMessages; } + private SortedMap move(MessageRange set, + final StoreMessageManager to, final MailboxSession session) throws MailboxException { + MessageMapper messageMapper = mapperFactory.getMessageMapper(session); + + final SortedMap movedMessages = new TreeMap(); + Iterator> originalRows = messageMapper.findInMailbox(mailbox, set, FetchType.Full, -1); + Iterator ids = to.move(originalRows, session); + while (ids.hasNext()) { + MessageMetaData data = ids.next(); + movedMessages.put(data.getUid(), data); + } + + return movedMessages; + } + + /** * Return the count of unseen messages * Index: apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java =================================================================== --- apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java (revision 1417296) +++ apache-james-mailbox/store/src/test/java/org/apache/james/mailbox/store/StoreMessageResultIteratorTest.java (working copy) @@ -148,6 +148,13 @@ } @Override + public MessageMetaData move(Mailbox mailbox, + Message original) throws MailboxException { + throw new UnsupportedOperationException(); + + } + + @Override public long getLastUid(Mailbox mailbox) throws MailboxException { throw new UnsupportedOperationException(); Index: protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java (revision 1417382) +++ protocols/imap/src/main/java/org/apache/james/imap/api/ImapConstants.java (working copy) @@ -203,6 +203,8 @@ public static final String COPY_COMMAND_NAME = "COPY"; + public static final String MOVE_COMMAND_NAME = "MOVE"; + public static final String CLOSE_COMMAND_NAME = "CLOSE"; public static final String CHECK_COMMAND_NAME = "CHECK"; Index: protocols/imap/src/main/java/org/apache/james/imap/decode/parser/CopyCommandParser.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/decode/parser/CopyCommandParser.java (revision 1417382) +++ protocols/imap/src/main/java/org/apache/james/imap/decode/parser/CopyCommandParser.java (working copy) @@ -33,9 +33,12 @@ public class CopyCommandParser extends AbstractUidCommandParser { public CopyCommandParser() { - super(ImapCommand.selectedStateCommand(ImapConstants.COPY_COMMAND_NAME)); + this(ImapCommand.selectedStateCommand(ImapConstants.COPY_COMMAND_NAME)); } + protected CopyCommandParser(ImapCommand command) { + super(command); + } /** * @see * org.apache.james.imap.decode.parser.AbstractUidCommandParser#decode(org.apache.james.imap.api.ImapCommand, @@ -46,8 +49,13 @@ IdRange[] idSet = request.parseIdRange(session); String mailboxName = request.mailbox(); request.eol(); - final ImapMessage result = new CopyRequest(command, idSet, mailboxName, useUids, tag); + final ImapMessage result = createRequest(command, tag, useUids, idSet, mailboxName); return result; } + protected CopyRequest createRequest(ImapCommand command, String tag, + boolean useUids, IdRange[] idSet, String mailboxName) { + return new CopyRequest(command, idSet, mailboxName, useUids, tag); } + +} Index: protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java (revision 1417382) +++ protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ImapParserFactory.java (working copy) @@ -85,6 +85,7 @@ _imapCommands.put(ImapConstants.CLOSE_COMMAND_NAME, CloseCommandParser.class); _imapCommands.put(ImapConstants.EXPUNGE_COMMAND_NAME, ExpungeCommandParser.class); _imapCommands.put(ImapConstants.COPY_COMMAND_NAME, CopyCommandParser.class); + _imapCommands.put(ImapConstants.MOVE_COMMAND_NAME, MoveCommandParser.class); _imapCommands.put(ImapConstants.SEARCH_COMMAND_NAME, SearchCommandParser.class); _imapCommands.put(ImapConstants.FETCH_COMMAND_NAME, FetchCommandParser.class); _imapCommands.put(ImapConstants.STORE_COMMAND_NAME, StoreCommandParser.class); Index: protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java (revision 0) +++ protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java (working copy) @@ -0,0 +1,22 @@ +package org.apache.james.imap.decode.parser; + +import org.apache.james.imap.api.ImapCommand; +import org.apache.james.imap.api.ImapConstants; +import org.apache.james.imap.api.message.IdRange; +import org.apache.james.imap.message.request.MoveRequest; + +/** + * Parse MOVE commands + */ +public class MoveCommandParser extends CopyCommandParser { + + public MoveCommandParser() { + super(ImapCommand.selectedStateCommand(ImapConstants.MOVE_COMMAND_NAME)); + } + + protected MoveRequest createRequest(ImapCommand command, String tag, + boolean useUids, IdRange[] idSet, String mailboxName) { + return new MoveRequest(command, idSet, mailboxName, useUids, tag); + } + +} Index: protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java (revision 0) +++ protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java (working copy) Property changes on: protocols/imap/src/main/java/org/apache/james/imap/decode/parser/MoveCommandParser.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java (revision 0) +++ protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java (working copy) @@ -0,0 +1,17 @@ +package org.apache.james.imap.message.request; + +import org.apache.james.imap.api.ImapCommand; +import org.apache.james.imap.api.message.IdRange; +import org.apache.james.imap.api.message.request.ImapRequest; + +/** + * {@link ImapRequest} which request the move of messages + */ +public class MoveRequest extends CopyRequest { + + public MoveRequest(ImapCommand command, IdRange[] idSet, + String mailboxName, boolean useUids, String tag) { + super(command, idSet, mailboxName, useUids, tag); + } + +} Index: protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java (revision 0) +++ protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java (working copy) Property changes on: protocols/imap/src/main/java/org/apache/james/imap/message/request/MoveRequest.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: protocols/imap/src/main/java/org/apache/james/imap/processor/CopyProcessor.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/processor/CopyProcessor.java (revision 1417382) +++ protocols/imap/src/main/java/org/apache/james/imap/processor/CopyProcessor.java (working copy) @@ -44,6 +44,10 @@ public class CopyProcessor extends AbstractMailboxProcessor { public CopyProcessor(final ImapProcessor next, final MailboxManager mailboxManager, final StatusResponseFactory factory) { + this(CopyRequest.class, next, mailboxManager, factory); + } + + protected CopyProcessor(final Class acceptableClass, final ImapProcessor next, final MailboxManager mailboxManager, final StatusResponseFactory factory) { super(CopyRequest.class, next, mailboxManager, factory); } @@ -74,8 +78,10 @@ for (int i = 0; i < idSet.length; i++) { MessageRange messageSet = messageRange(currentMailbox, idSet[i], useUids); if (messageSet != null) { - List copiedUids = mailboxManager.copyMessages(messageSet, currentMailbox.getPath(), targetMailbox, mailboxSession); - for (MessageRange mr : copiedUids) { + List processedUids = process( + targetMailbox, currentMailbox, mailboxSession, + mailboxManager, messageSet); + for (MessageRange mr : processedUids) { // Set recent flag on copied message as this SHOULD be // done. // See RFC 3501 6.4.7. COPY Command @@ -108,4 +114,13 @@ no(command, tag, responder, HumanReadableText.GENERIC_FAILURE_DURING_PROCESSING); } } + + protected List process(final MailboxPath targetMailbox, + final SelectedMailbox currentMailbox, + final MailboxSession mailboxSession, + final MailboxManager mailboxManager, MessageRange messageSet) + throws MailboxException { + List processedUids = mailboxManager.copyMessages(messageSet, currentMailbox.getPath(), targetMailbox, mailboxSession); + return processedUids; } +} Index: protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java (revision 1417382) +++ protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessorChain.java (working copy) @@ -52,7 +52,8 @@ final UnsubscribeProcessor unsubscribeProcessor = new UnsubscribeProcessor(closeProcessor, mailboxManager, subscriptionManager, statusResponseFactory); final SubscribeProcessor subscribeProcessor = new SubscribeProcessor(unsubscribeProcessor, mailboxManager, subscriptionManager, statusResponseFactory); final CopyProcessor copyProcessor = new CopyProcessor(subscribeProcessor, mailboxManager, statusResponseFactory); - final AuthenticateProcessor authenticateProcessor = new AuthenticateProcessor(copyProcessor, mailboxManager, statusResponseFactory); + final MoveProcessor moveProcessor = new MoveProcessor(copyProcessor, mailboxManager, statusResponseFactory); + final AuthenticateProcessor authenticateProcessor = new AuthenticateProcessor(moveProcessor, mailboxManager, statusResponseFactory); final ExpungeProcessor expungeProcessor = new ExpungeProcessor(authenticateProcessor, mailboxManager, statusResponseFactory); final ExamineProcessor examineProcessor = new ExamineProcessor(expungeProcessor, mailboxManager, statusResponseFactory); final AppendProcessor appendProcessor = new AppendProcessor(examineProcessor, mailboxManager, statusResponseFactory); Index: protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java (revision 0) +++ protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java (working copy) @@ -0,0 +1,31 @@ +package org.apache.james.imap.processor; + +import java.util.List; + +import org.apache.james.imap.api.message.response.StatusResponseFactory; +import org.apache.james.imap.api.process.ImapProcessor; +import org.apache.james.imap.api.process.SelectedMailbox; +import org.apache.james.imap.message.request.MoveRequest; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MessageRange; + +public class MoveProcessor extends CopyProcessor { + + public MoveProcessor(ImapProcessor next, MailboxManager mailboxManager, + StatusResponseFactory factory) { + super(MoveRequest.class, next, mailboxManager, factory); + } + + protected List process(final MailboxPath targetMailbox, + final SelectedMailbox currentMailbox, + final MailboxSession mailboxSession, + final MailboxManager mailboxManager, MessageRange messageSet) + throws MailboxException { + List processedUids = mailboxManager.moveMessages(messageSet, currentMailbox.getPath(), targetMailbox, mailboxSession); + return processedUids; + } + +} Index: protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java =================================================================== --- protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java (revision 0) +++ protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java (working copy) Property changes on: protocols/imap/src/main/java/org/apache/james/imap/processor/MoveProcessor.java ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +text/plain \ No newline at end of property Index: protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java =================================================================== --- protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java (revision 1417382) +++ protocols/imap/src/test/java/org/apache/james/imap/processor/base/MailboxEventAnalyserTest.java (working copy) @@ -346,6 +346,10 @@ throw new UnsupportedOperationException("Not implemented"); } + public List moveMessages(MessageRange set, MailboxPath from, MailboxPath to, MailboxSession session) throws MailboxException { + throw new UnsupportedOperationException("Not implemented"); + + } }; private final class MyMailboxSession implements MailboxSession { private long sessionId;