This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 8604f845a63bbbeaf3ffce6f2279d1d9a9e07b1c Author: Benoit Tellier <btell...@linagora.com> AuthorDate: Thu Nov 24 14:58:55 2022 +0700 JAMES-3754 RFC-5819 IMAP4 Extension for Returning STATUS Information in Extended LIST --- .../org/apache/james/mailbox/MessageManager.java | 2 +- .../org/apache/james/imap/scripts/Status.test | 9 ++++++ .../imap/decode/parser/ListCommandParser.java | 2 +- .../imap/decode/parser/StatusCommandParser.java | 32 +++++++++++----------- .../james/imap/processor/DefaultProcessor.java | 5 ++-- .../apache/james/imap/processor/ListProcessor.java | 20 +++++++++----- .../james/imap/processor/StatusProcessor.java | 30 +++++++++++--------- .../james/imap/processor/XListProcessor.java | 2 +- 8 files changed, 61 insertions(+), 41 deletions(-) diff --git a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java index e2d666df6e..65f3b03843 100644 --- a/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java +++ b/mailbox/api/src/main/java/org/apache/james/mailbox/MessageManager.java @@ -451,7 +451,7 @@ public interface MessageManager { /** * Gets the path of the referenced mailbox */ - MailboxPath getMailboxPath() throws MailboxException; + MailboxPath getMailboxPath(); Flags getApplicableFlags(MailboxSession session) throws MailboxException; diff --git a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test index ea37d3085d..89eb6f8640 100644 --- a/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test +++ b/mpt/impl/imap-mailbox/core/src/main/resources/org/apache/james/imap/scripts/Status.test @@ -126,6 +126,15 @@ C: a014 STATUS statustest (MAILBOXID) S: \* STATUS \"statustest\" \(MAILBOXID \(.+\)\) S: a014 OK STATUS completed. +C: a015 LIST "" * RETURN (STATUS (UNSEEN SIZE MESSAGES DELETED DELETED-STORAGE MAILBOXID)) +SUB { +S: \* LIST \(\\HasNoChildren\) "." "INBOX" +S: \* STATUS \"INBOX\" \(MESSAGES 0 SIZE 0 DELETED 0 DELETED-STORAGE 0 UNSEEN 0 MAILBOXID \(.+\)\) +S: \* LIST \(\\HasNoChildren\) "." "statustest" +S: \* STATUS "statustest" \(MESSAGES 4 SIZE 1016 DELETED 2 DELETED-STORAGE 508 UNSEEN 4 MAILBOXID \(.+\)\) +} +S: a015 OK LIST completed. + # Cleanup C: a1 DELETE statustest S: a1 OK DELETE completed. diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java index f147718763..95d23e1cd9 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/ListCommandParser.java @@ -146,7 +146,7 @@ public class ListCommandParser extends AbstractUidCommandParser { assertChar(request, 'T', 't'); assertChar(request, 'U', 'u'); assertChar(request, 'S', 's'); - return Pair.of(ListReturnOption.STATUS, Optional.empty()); + return Pair.of(ListReturnOption.STATUS, Optional.of(StatusCommandParser.statusDataItems(request))); } private ListSelectOption readSubscribed(ImapRequestLineReader request) throws DecodingException { diff --git a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java index b5e1800a84..9fca72d3d7 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/decode/parser/StatusCommandParser.java @@ -49,11 +49,11 @@ public class StatusCommandParser extends AbstractImapCommandParser { return new StatusRequest(mailboxName, statusDataItems, tag); } - private StatusDataItems statusDataItems(ImapRequestLineReader request) throws DecodingException { + static StatusDataItems statusDataItems(ImapRequestLineReader request) throws DecodingException { return new StatusDataItems(splitWords(request)); } - private EnumSet<StatusDataItems.StatusItem> splitWords(ImapRequestLineReader request) throws DecodingException { + private static EnumSet<StatusDataItems.StatusItem> splitWords(ImapRequestLineReader request) throws DecodingException { EnumSet<StatusDataItems.StatusItem> words = EnumSet.noneOf(StatusDataItems.StatusItem.class); request.nextWordChar(); @@ -68,7 +68,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return words; } - private StatusDataItems.StatusItem parseStatus(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem parseStatus(ImapRequestLineReader request) throws DecodingException { // All the matching must be done in a case-insensitive fashion. // See rfc3501 9. Formal Syntax and IMAP-282 char c = request.nextWordChar(); @@ -96,7 +96,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unknown status item: '" + request.consumeWord(ImapRequestLineReader.NoopCharValidator.INSTANCE) + "'"); } - private StatusDataItems.StatusItem readM(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readM(ImapRequestLineReader request) throws DecodingException { request.consume(); char c2 = request.nextChar(); if (c2 == 'e' || c2 == 'E') { @@ -106,7 +106,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { } } - private StatusDataItems.StatusItem readU(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readU(ImapRequestLineReader request) throws DecodingException { char c; assertChar(request, 'u', 'U'); c = request.nextWordChar(); @@ -123,7 +123,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.UID_VALIDITY; } - private void readValidity(ImapRequestLineReader request) throws DecodingException { + private static void readValidity(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'v', 'V'); assertChar(request, 'a', 'A'); assertChar(request, 'l', 'L'); @@ -134,7 +134,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { assertChar(request, 'y', 'Y'); } - private StatusDataItems.StatusItem readAppendLimit(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readAppendLimit(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'a', 'A'); assertChar(request, 'p', 'P'); assertChar(request, 'p', 'P'); @@ -149,7 +149,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.APPENDLIMIT; } - private StatusDataItems.StatusItem readSize(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readSize(ImapRequestLineReader request) throws DecodingException { assertChar(request, 's', 'S'); assertChar(request, 'i', 'I'); assertChar(request, 'z', 'Z'); @@ -157,7 +157,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.SIZE; } - private StatusDataItems.StatusItem readUidNext(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readUidNext(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'n', 'N'); assertChar(request, 'e', 'E'); assertChar(request, 'x', 'X'); @@ -165,7 +165,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.UID_NEXT; } - private StatusDataItems.StatusItem readUnseen(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readUnseen(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'n', 'N'); assertChar(request, 's', 'S'); assertChar(request, 'e', 'E'); @@ -174,7 +174,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.UNSEEN; } - private StatusDataItems.StatusItem readHighestModseq(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readHighestModseq(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'h', 'H'); assertChar(request, 'i', 'I'); assertChar(request, 'g', 'G'); @@ -191,7 +191,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.HIGHEST_MODSEQ; } - private StatusDataItems.StatusItem readRecent(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readRecent(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'r', 'R'); assertChar(request, 'e', 'E'); assertChar(request, 'c', 'C'); @@ -201,7 +201,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.RECENT; } - private StatusDataItems.StatusItem readMessages(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readMessages(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'e', 'E'); assertChar(request, 's', 'S'); assertChar(request, 's', 'S'); @@ -212,7 +212,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.MESSAGES; } - private StatusDataItems.StatusItem readDeleted(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readDeleted(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'd', 'D'); assertChar(request, 'e', 'E'); assertChar(request, 'l', 'L'); @@ -235,7 +235,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.DELETED; } - private StatusDataItems.StatusItem readMailboxId(ImapRequestLineReader request) throws DecodingException { + private static StatusDataItems.StatusItem readMailboxId(ImapRequestLineReader request) throws DecodingException { assertChar(request, 'a', 'A'); assertChar(request, 'i', 'I'); assertChar(request, 'l', 'L'); @@ -247,7 +247,7 @@ public class StatusCommandParser extends AbstractImapCommandParser { return StatusDataItems.StatusItem.MAILBOXID; } - private void assertChar(ImapRequestLineReader reader, char low, char up) throws DecodingException { + private static void assertChar(ImapRequestLineReader reader, char low, char up) throws DecodingException { char c = reader.consume(); if (c != low && c != up) { throw new DecodingException(HumanReadableText.ILLEGAL_ARGUMENTS, "Unexpected token in Status item. Expecting " + up + " got " + c); diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java index 356d161990..40d153c8c0 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/DefaultProcessor.java @@ -79,10 +79,11 @@ public class DefaultProcessor implements ImapProcessor { builder.add(new StoreProcessor(mailboxManager, statusResponseFactory, metricFactory)); builder.add(new NoopProcessor(mailboxManager, statusResponseFactory, metricFactory)); builder.add(new IdleProcessor(mailboxManager, statusResponseFactory, metricFactory)); - builder.add(new StatusProcessor(mailboxManager, statusResponseFactory, metricFactory)); + StatusProcessor statusProcessor = new StatusProcessor(mailboxManager, statusResponseFactory, metricFactory); + builder.add(statusProcessor); builder.add(new LSubProcessor(mailboxManager, subscriptionManager, statusResponseFactory, metricFactory)); builder.add(new XListProcessor(mailboxManager, statusResponseFactory, mailboxTyper, metricFactory, subscriptionManager)); - builder.add(new ListProcessor<>(mailboxManager, statusResponseFactory, metricFactory, subscriptionManager)); + builder.add(new ListProcessor<>(mailboxManager, statusResponseFactory, metricFactory, subscriptionManager, statusProcessor)); builder.add(new SearchProcessor(mailboxManager, statusResponseFactory, metricFactory)); SelectProcessor selectProcessor = new SelectProcessor(mailboxManager, eventBus, statusResponseFactory, metricFactory); builder.add(selectProcessor); diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java index c4bb28302f..9ed9551e89 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/ListProcessor.java @@ -70,16 +70,20 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess private static final List<Capability> CAPA = ImmutableList.of(Capability.of("LIST-EXTENDED"), Capability.of("LIST-STATUS")); private final SubscriptionManager subscriptionManager; + private final StatusProcessor statusProcessor; public ListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, - MetricFactory metricFactory, SubscriptionManager subscriptionManager) { - this((Class<T>) ListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager); + MetricFactory metricFactory, SubscriptionManager subscriptionManager, + StatusProcessor statusProcessor) { + this((Class<T>) ListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager, statusProcessor); } public ListProcessor(Class<T> clazz, MailboxManager mailboxManager, StatusResponseFactory factory, - MetricFactory metricFactory, SubscriptionManager subscriptionManager) { + MetricFactory metricFactory, SubscriptionManager subscriptionManager, + StatusProcessor statusProcessor) { super(clazz, mailboxManager, factory, metricFactory); this.subscriptionManager = subscriptionManager; + this.statusProcessor = statusProcessor; } @Override @@ -177,13 +181,13 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess request.getMailboxPattern(), mailboxSession); if (request.selectSubscribed()) { - return processWithSubscribed(request, responder, mailboxSession, isRelative, mailboxQuery); + return processWithSubscribed(session, request, responder, mailboxSession, isRelative, mailboxQuery); } else { - return processWithoutSubscribed(session, responder, mailboxSession, isRelative, mailboxQuery); + return processWithoutSubscribed(session, request, responder, mailboxSession, isRelative, mailboxQuery); } } - private Mono<Void> processWithoutSubscribed(ImapSession session, Responder responder, MailboxSession mailboxSession, boolean isRelative, MailboxQuery mailboxQuery) { + private Mono<Void> processWithoutSubscribed(ImapSession session, T request, Responder responder, MailboxSession mailboxSession, boolean isRelative, MailboxQuery mailboxQuery) { return getMailboxManager().search(mailboxQuery, Minimal, mailboxSession) .doOnNext(metaData -> responder.respond( createResponse(metaData.inferiors(), @@ -191,16 +195,18 @@ public class ListProcessor<T extends ListRequest> extends AbstractMailboxProcess mailboxName(isRelative, metaData.getPath(), metaData.getHierarchyDelimiter()), metaData.getHierarchyDelimiter(), getMailboxType(session, metaData.getPath())))) + .flatMap(metaData -> request.getStatusDataItems().map(statusDataItems -> statusProcessor.sendStatus(metaData.getPath(), statusDataItems, responder, session, mailboxSession)).orElse(Mono.empty())) .then(); } - private Mono<Void> processWithSubscribed(T request, Responder responder, MailboxSession mailboxSession, boolean isRelative, MailboxQuery mailboxQuery) { + private Mono<Void> processWithSubscribed(ImapSession session, T request, Responder responder, MailboxSession mailboxSession, boolean isRelative, MailboxQuery mailboxQuery) { return Mono.zip(getMailboxManager().search(mailboxQuery, Minimal, mailboxSession).collectList() .map(searchedResultList -> searchedResultList.stream().collect(Collectors.toMap(MailboxMetaData::getPath, Function.identity()))), Flux.from(Throwing.supplier(() -> subscriptionManager.subscriptionsReactive(mailboxSession)).get()).collectList()) .map(tuple -> getListResponseForSelectSubscribed(tuple.getT1(), tuple.getT2(), request, mailboxSession, isRelative, mailboxQuery)) .flatMapIterable(list -> list) .doOnNext(pathAndResponse -> responder.respond(pathAndResponse.getRight())) + .flatMap(pathAndResponse -> request.getStatusDataItems().map(statusDataItems -> statusProcessor.sendStatus(pathAndResponse.getLeft(), statusDataItems, responder, session, mailboxSession)).orElse(Mono.empty())) .then(); } diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java index 7feeb4afbc..541f84f421 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/StatusProcessor.java @@ -94,18 +94,10 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp protected Mono<Void> processRequestReactive(StatusRequest request, ImapSession session, Responder responder) { MailboxPath mailboxPath = PathConverter.forSession(session).buildFullPath(request.getMailboxName()); MailboxSession mailboxSession = session.getMailboxSession(); + StatusDataItems statusDataItems = request.getStatusDataItems(); return logInitialRequest(mailboxPath) - .then(Mono.from(getMailboxManager().getMailboxReactive(mailboxPath, mailboxSession))) - .flatMap(mailbox -> retrieveMetadata(mailbox, request.getStatusDataItems(), mailboxSession) - .flatMap(metaData -> computeStatusResponse(mailbox, request, metaData, mailboxSession) - .doOnNext(response -> { - // Enable CONDSTORE as this is a CONDSTORE enabling command - if (response.getHighestModSeq() != null) { - condstoreEnablingCommand(session, responder, metaData, false); - } - responder.respond(response); - }))) + .then(sendStatus(mailboxPath, statusDataItems, responder, session, mailboxSession)) .then(unsolicitedResponses(session, responder, false)) .then(Mono.fromRunnable(() -> okComplete(request, responder))) .onErrorResume(MailboxException.class, e -> { @@ -115,6 +107,19 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp .then(); } + Mono<MailboxStatusResponse> sendStatus(MailboxPath mailboxPath, StatusDataItems statusDataItems, Responder responder, ImapSession session, MailboxSession mailboxSession) { + return Mono.from(getMailboxManager().getMailboxReactive(mailboxPath, mailboxSession)) + .flatMap(mailbox -> retrieveMetadata(mailbox, statusDataItems, mailboxSession) + .flatMap(metaData -> computeStatusResponse(mailbox, statusDataItems, metaData, mailboxSession) + .doOnNext(response -> { + // Enable CONDSTORE as this is a CONDSTORE enabling command + if (response.getHighestModSeq() != null) { + condstoreEnablingCommand(session, responder, metaData, false); + } + responder.respond(response); + }))); + } + private Mono<Void> logInitialRequest(MailboxPath mailboxPath) { if (LOGGER.isDebugEnabled()) { return ReactorUtils.logAsMono(() -> LOGGER.debug("Status called on mailbox named {}", mailboxPath)); @@ -142,10 +147,9 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp } private Mono<MailboxStatusResponse> computeStatusResponse(MessageManager mailbox, - StatusRequest request, + StatusDataItems statusDataItems, MessageManager.MailboxMetaData metaData, MailboxSession session) { - StatusDataItems statusDataItems = request.getStatusDataItems(); return iterateMailbox(statusDataItems, mailbox, session) .map(maybeIterationResult -> { Optional<Long> appendLimit = appendLimit(statusDataItems); @@ -161,7 +165,7 @@ public class StatusProcessor extends AbstractMailboxProcessor<StatusRequest> imp maybeIterationResult.flatMap(result -> result.getSize(statusDataItems)).orElse(null), maybeIterationResult.flatMap(result -> result.getDeleted(statusDataItems)).orElse(null), maybeIterationResult.flatMap(result -> result.getDeletedStorage(statusDataItems)).orElse(null), - messages, recent, uidNext, highestModSeq, uidValidity, unseen, request.getMailboxName(), mailboxId); + messages, recent, uidNext, highestModSeq, uidValidity, unseen, mailbox.getMailboxPath().getName(), mailboxId); }); } diff --git a/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java b/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java index e164522dc7..7bab0ff87e 100644 --- a/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java +++ b/protocols/imap/src/main/java/org/apache/james/imap/processor/XListProcessor.java @@ -49,7 +49,7 @@ public class XListProcessor extends ListProcessor<XListRequest> implements Capab public XListProcessor(MailboxManager mailboxManager, StatusResponseFactory factory, MailboxTyper mailboxTyper, MetricFactory metricFactory, SubscriptionManager subscriptionManager) { - super(XListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager); + super(XListRequest.class, mailboxManager, factory, metricFactory, subscriptionManager, null); this.mailboxTyper = mailboxTyper; } --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org