Repository: james-project Updated Branches: refs/heads/master 51cc52ae3 -> fb73fe9e1
JAMES-2218 More clever SetMessages update validation to accept all valid states Moving a non draft message to a draft mailbox while marking it as draft should be tolerated Moving a draft message out of draft mailbox while marking it as non-draft should be tolerated Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/7c1685b4 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/7c1685b4 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/7c1685b4 Branch: refs/heads/master Commit: 7c1685b442269edc382643ca39e9f6b1b7b275da Parents: e505089 Author: benwa <btell...@linagora.com> Authored: Thu Nov 16 14:20:26 2017 +0700 Committer: Antoine Duprat <adup...@linagora.com> Committed: Thu Nov 16 12:28:58 2017 +0100 ---------------------------------------------------------------------- .../cucumber/SetMessagesMethodStepdefs.java | 69 +++++++++++---- .../test/resources/cucumber/SetMessages.feature | 23 +++-- .../DraftMessageMailboxUpdateException.java | 4 +- .../methods/SetMessagesUpdateProcessor.java | 91 +++++++++++++++----- .../james/jmap/model/UpdateMessagePatch.java | 13 +-- 5 files changed, 146 insertions(+), 54 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/7c1685b4/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMessagesMethodStepdefs.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMessagesMethodStepdefs.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMessagesMethodStepdefs.java index ff30634..8cdd65a 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMessagesMethodStepdefs.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/cucumber/SetMessagesMethodStepdefs.java @@ -59,12 +59,12 @@ public class SetMessagesMethodStepdefs { this.messageIdStepdefs = messageIdStepdefs; } - @When("^\"([^\"]*)\" moves \"([^\"]*)\" to user mailbox \"([^\"]*)\"") + @When("^\"([^\"]*)\" moves \"([^\"]*)\" to user mailbox \"([^\"]*)\"$") public void moveMessageToMailboxWithUser(String username, String message, String mailbox) throws Throwable { userStepdefs.execWithUser(username, () -> moveMessageToMailbox(message, mailbox)); } - @When("^the user moves \"([^\"]*)\" to user mailbox \"([^\"]*)\"") + @When("^the user moves \"([^\"]*)\" to user mailbox \"([^\"]*)\"$") public void moveMessageToMailbox(String message, String mailbox) throws Throwable { MessageId messageId = messageIdStepdefs.getMessageId(message); MailboxId mailboxId = mainStepdefs.jmapServer @@ -86,12 +86,36 @@ public class SetMessagesMethodStepdefs { mainStepdefs.awaitMethod.run(); } - @When("^\"([^\"]*)\" copies \"([^\"]*)\" from mailbox \"([^\"]*)\" to mailbox \"([^\"]*)\"") + @When("^the user moves \"([^\"]*)\" to user mailbox \"([^\"]*)\" and set flags \"([^\"]*)\"$") + public void moveMessageToMailboxAndChangeFlags(String message, String mailbox, List<String> keywords) throws Throwable { + MessageId messageId = messageIdStepdefs.getMessageId(message); + MailboxId mailboxId = mainStepdefs.jmapServer + .getProbe(MailboxProbeImpl.class) + .getMailbox(MailboxConstants.USER_NAMESPACE, userStepdefs.getConnectedUser(), mailbox) + .getMailboxId(); + String keywordString = toKeywordsString(keywords); + + httpClient.post("[" + + " [" + + " \"setMessages\","+ + " {" + + " \"update\": { \"" + messageId.serialize() + "\" : {" + + " \"mailboxIds\": [\"" + mailboxId.serialize() + "\"]," + + " \"keywords\": {" + keywordString + "}" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"); + mainStepdefs.awaitMethod.run(); + } + + @When("^\"([^\"]*)\" copies \"([^\"]*)\" from mailbox \"([^\"]*)\" to mailbox \"([^\"]*)\"$") public void copyMessageToMailbox(String username, String message, String sourceMailbox, String destinationMailbox) throws Throwable { userStepdefs.execWithUser(username, () -> copyMessageToMailbox(message, sourceMailbox, destinationMailbox)); } - @When("^the user copies \"([^\"]*)\" from mailbox \"([^\"]*)\" to mailbox \"([^\"]*)\"") + @When("^the user copies \"([^\"]*)\" from mailbox \"([^\"]*)\" to mailbox \"([^\"]*)\"$") public void copyMessageToMailbox(String message, String sourceMailbox, String destinationMailbox) throws Throwable { MessageId messageId = messageIdStepdefs.getMessageId(message); MailboxId sourceMailboxId = mainStepdefs.jmapServer @@ -117,7 +141,7 @@ public class SetMessagesMethodStepdefs { mainStepdefs.awaitMethod.run(); } - @When("^\"([^\"]*)\" copies \"([^\"]*)\" from mailbox \"([^\"]*)\" of user \"([^\"]*)\" to mailbox \"([^\"]*)\" of user \"([^\"]*)\"") + @When("^\"([^\"]*)\" copies \"([^\"]*)\" from mailbox \"([^\"]*)\" of user \"([^\"]*)\" to mailbox \"([^\"]*)\" of user \"([^\"]*)\"$") public void copyMessageToMailbox(String username, String message, String sourceMailbox, String sourceUser, String destinationMailbox, String destinationUser) throws Throwable { userStepdefs.execWithUser(username, () -> copyMessageToMailbox(message, sourceMailbox, sourceUser, destinationMailbox, destinationUser)); } @@ -147,7 +171,7 @@ public class SetMessagesMethodStepdefs { mainStepdefs.awaitMethod.run(); } - @Given("^\"([^\"]*)\" moves \"([^\"]*)\" to mailbox \"([^\"]*)\" of user \"([^\"]*)\"") + @Given("^\"([^\"]*)\" moves \"([^\"]*)\" to mailbox \"([^\"]*)\" of user \"([^\"]*)\"$") public void moveMessageToMailbox(String username, String message, String destinationMailbox, String destinationUser) throws Throwable { userStepdefs.execWithUser(username, () -> moveMessageToMailbox(message, destinationMailbox, destinationUser)); } @@ -173,12 +197,12 @@ public class SetMessagesMethodStepdefs { mainStepdefs.awaitMethod.run(); } - @When("^\"([^\"]*)\" sets flags \"([^\"]*)\" on message \"([^\"]*)\"") + @When("^\"([^\"]*)\" sets flags \"([^\"]*)\" on message \"([^\"]*)\"$") public void setFlags(String username, List<String> keywords, String message) throws Throwable { userStepdefs.execWithUser(username, () -> setFlags(keywords, message)); } - @When("^\"([^\"]*)\" destroys message \"([^\"]*)\"") + @When("^\"([^\"]*)\" destroys message \"([^\"]*)\"$") public void destroyMessage(String username, String message) throws Throwable { MessageId messageId = messageIdStepdefs.getMessageId(message); userStepdefs.execWithUser(username, () -> { @@ -195,7 +219,7 @@ public class SetMessagesMethodStepdefs { }); } - @Given("^\"([^\"]*)\" creates a draft message \"([^\"]*)\" in mailbox \"([^\"]*)\"") + @Given("^\"([^\"]*)\" tries to create a draft message \"([^\"]*)\" in mailbox \"([^\"]*)\"$") public void createDraft(String username, String message, String mailboxName) throws Throwable { userStepdefs.execWithUser(username, () -> { Mailbox mailbox = mainStepdefs.mailboxProbe.getMailbox(MailboxConstants.USER_NAMESPACE, @@ -222,13 +246,10 @@ public class SetMessagesMethodStepdefs { }); } - @When("^the user sets flags \"([^\"]*)\" on message \"([^\"]*)\"") + @When("^the user sets flags \"([^\"]*)\" on message \"([^\"]*)\"$") public void setFlags(List<String> keywords, String message) throws Throwable { MessageId messageId = messageIdStepdefs.getMessageId(message); - String keywordString = keywords - .stream() - .map(value -> "\"" + value + "\" : true") - .collect(Collectors.joining(",")); + String keywordString = toKeywordsString(keywords); httpClient.post("[" + " [" + @@ -244,7 +265,14 @@ public class SetMessagesMethodStepdefs { mainStepdefs.awaitMethod.run(); } - @When("^message \"([^\"]*)\" has flags (.*) in mailbox \"([^\"]*)\" of user \"([^\"]*)\"") + private String toKeywordsString(List<String> keywords) { + return keywords + .stream() + .map(value -> "\"" + value + "\" : true") + .collect(Collectors.joining(",")); + } + + @When("^message \"([^\"]*)\" has flags (.*) in mailbox \"([^\"]*)\" of user \"([^\"]*)\"$") public void setMessageFlagsInSpecifiedMailbox(String message, List<String> flags, String mailbox, String mailboxOwner) throws Exception { Flags newFlags = Keywords.factory().fromList(flags).asFlags(); String username = userStepdefs.getConnectedUser(); @@ -259,13 +287,20 @@ public class SetMessagesMethodStepdefs { } @Then("^message \"([^\"]*)\" is not updated$") - public void assertIdOfTheFirstMessage(String messageName) throws Exception { + public void assertNotUpdate(String messageName) throws Exception { MessageId id = messageIdStepdefs.getMessageId(messageName); assertThat(httpClient.jsonPath.<Map<String, String>>read("[0][1].notUpdated")) .containsOnlyKeys(id.serialize()); } - @Then("^message \"([^\"]*)\" is not created") + @Then("^message \"([^\"]*)\" is updated$") + public void assertUpdated(String messageName) throws Exception { + MessageId id = messageIdStepdefs.getMessageId(messageName); + assertThat(httpClient.jsonPath.<List<String>>read("[0][1].updated")) + .containsOnly(id.serialize()); + } + + @Then("^message \"([^\"]*)\" is not created$") public void assertNotCreated(String messageName) throws Exception {; assertThat(httpClient.jsonPath.<Map<String, String>>read("[0][1].notCreated")) .containsOnlyKeys(messageName); http://git-wip-us.apache.org/repos/asf/james-project/blob/7c1685b4/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/SetMessages.feature ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/SetMessages.feature b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/SetMessages.feature index ac816e4..2fdc88d 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/SetMessages.feature +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/resources/cucumber/SetMessages.feature @@ -106,38 +106,49 @@ Feature: SetMessages method on shared folders Scenario: A user can update the flags on a draft Given "b...@domain.tld" has a mailbox "Drafts" - And "b...@domain.tld" creates a draft message "mDraft" in mailbox "Drafts" + And "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Drafts" When "b...@domain.tld" sets flags "$Draft,$Seen" on message "mDraft" Then "b...@domain.tld" should see message "mDraft" with keywords $Draft,$Seen Scenario: A user can not remove a draft flag on a draft messages Given "b...@domain.tld" has a mailbox "Drafts" - And "b...@domain.tld" creates a draft message "mDraft" in mailbox "Drafts" + And "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Drafts" When "b...@domain.tld" sets flags "$Seen" on message "mDraft" Then message "mDraft" is not updated And "b...@domain.tld" should see message "mDraft" with keywords $Draft Scenario: A user can destroy a draft Given "b...@domain.tld" has a mailbox "Drafts" - And "b...@domain.tld" creates a draft message "mDraft" in mailbox "Drafts" + And "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Drafts" When "b...@domain.tld" destroys message "mDraft" Then "b...@domain.tld" ask for message "mDraft" And the notFound list should contain "mDraft" Scenario: Draft creation in outbox is not allowed Given "b...@domain.tld" has a mailbox "Outbox" - When "b...@domain.tld" creates a draft message "mDraft" in mailbox "Outbox" + When "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Outbox" Then message "mDraft" is not created Scenario: A user can not move draft out of draft mailbox Given "b...@domain.tld" has a mailbox "Drafts" - And "b...@domain.tld" creates a draft message "mDraft" in mailbox "Drafts" + And "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Drafts" When "b...@domain.tld" moves "mDraft" to user mailbox "shared" Then message "mDraft" is not updated + Scenario: A user can move draft out of draft mailbox when removing draft flag + Given "b...@domain.tld" has a mailbox "Drafts" + And "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Drafts" + When the user moves "mDraft" to user mailbox "shared" and set flags "" + Then message "mDraft" is updated + + Scenario: A user can move non-draft messages to draft mailbox when setting $Draft + Given "b...@domain.tld" has a mailbox "Drafts" + When the user moves "mBob" to user mailbox "Drafts" and set flags "$Draft" + Then message "mBob" is updated + Scenario: A user can not copy draft out of draft mailbox Given "b...@domain.tld" has a mailbox "Drafts" - And "b...@domain.tld" creates a draft message "mDraft" in mailbox "Drafts" + And "b...@domain.tld" tries to create a draft message "mDraft" in mailbox "Drafts" When "b...@domain.tld" copies "mDraft" from mailbox "Drafts" to mailbox "shared" Then message "mDraft" is not updated http://git-wip-us.apache.org/repos/asf/james-project/blob/7c1685b4/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/DraftMessageMailboxUpdateException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/DraftMessageMailboxUpdateException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/DraftMessageMailboxUpdateException.java index 296dc9c..862a282 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/DraftMessageMailboxUpdateException.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/DraftMessageMailboxUpdateException.java @@ -23,7 +23,7 @@ import org.apache.james.mailbox.exception.MailboxException; public class DraftMessageMailboxUpdateException extends MailboxException { - public DraftMessageMailboxUpdateException() { - super(); + public DraftMessageMailboxUpdateException(String message) { + super(message); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/7c1685b4/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java index f23ba55..2497cea 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesUpdateProcessor.java @@ -100,6 +100,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { SetMessagesResponse.Builder builder) { try { List<MessageResult> messages = messageIdManager.getMessages(ImmutableList.of(messageId), FetchGroupImpl.MINIMAL, mailboxSession); + assertValidUpdate(messages, updateMessagePatch, mailboxSession); if (messages.isEmpty()) { addMessageIdNotFoundToResponse(messageId, builder); @@ -117,7 +118,7 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { } } } catch (DraftMessageMailboxUpdateException e) { - handleDraftMessageMailboxUpdateException(messageId, builder); + handleDraftMessageMailboxUpdateException(messageId, builder, e); } catch (MailboxException e) { handleMessageUpdateException(messageId, builder, e); } catch (IllegalArgumentException e) { @@ -131,6 +132,69 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { } + private void assertValidUpdate(List<MessageResult> messagesToBeUpdated, UpdateMessagePatch updateMessagePatch, MailboxSession session) throws MailboxException { + List<MailboxId> draftMailboxes = mailboxIdFor(Role.DRAFTS, session); + List<Flags> futureFlags = patchFlags(messagesToBeUpdated, updateMessagePatch); + List<MailboxId> targetMailboxes = getTargetedMailboxes(messagesToBeUpdated, updateMessagePatch); + + boolean targetHasDraft = targetMailboxes.stream().anyMatch(draftMailboxes::contains); + boolean targetHasNonDraft = targetMailboxes.stream().anyMatch(id -> !draftMailboxes.contains(id)); + + assertValidUpdate(futureFlags, targetHasDraft, targetHasNonDraft); + } + + private void assertValidUpdate(List<Flags> futureFlags, boolean targetHasDraft, boolean targetHasNonDraft) throws DraftMessageMailboxUpdateException { + assertMessageIsNotInDraftAndNonDraftMailboxes(targetHasDraft, targetHasNonDraft); + assertNoNonDraftMessageInsideDraftMailbox(futureFlags, targetHasDraft); + assertNoDraftMessageOutOfDraftMailbox(futureFlags, targetHasNonDraft); + } + + private void assertMessageIsNotInDraftAndNonDraftMailboxes(boolean targetHasDraft, boolean targetHasNonDraft) throws DraftMessageMailboxUpdateException { + if (targetHasDraft && targetHasNonDraft) { + throw new DraftMessageMailboxUpdateException("One can not have a message in mailboxes that don't have all the `draft` role"); + } + } + + private void assertNoNonDraftMessageInsideDraftMailbox(List<Flags> futureFlags, boolean targetHasDraft) throws DraftMessageMailboxUpdateException { + if (targetHasDraft) { + boolean anyNonDraftMessage = futureFlags.stream().anyMatch(flags -> !flags.contains(Flags.Flag.DRAFT)); + if (anyNonDraftMessage) { + throw new DraftMessageMailboxUpdateException("Messages without '$Draft' keyword are prohibited in drafts mailboxes"); + } + } + } + + private void assertNoDraftMessageOutOfDraftMailbox(List<Flags> futureFlags, boolean targetHasNonDraft) throws DraftMessageMailboxUpdateException { + if (targetHasNonDraft) { + boolean anyDraftMessage = futureFlags.stream().anyMatch(flags -> flags.contains(Flags.Flag.DRAFT)); + if (anyDraftMessage) { + throw new DraftMessageMailboxUpdateException("Messages with '$Draft' keyword are prohibited in non drafts mailboxes"); + } + } + } + + private List<MailboxId> getTargetedMailboxes(List<MessageResult> messagesToBeUpdated, UpdateMessagePatch updateMessagePatch) { + ImmutableList<MailboxId> previousMailboxes = messagesToBeUpdated.stream() + .map(MessageResult::getMailboxId) + .collect(Guavate.toImmutableList()); + return updateMessagePatch.getMailboxIds() + .map(ids -> ids.stream().map(mailboxIdFactory::fromString).collect(Guavate.toImmutableList())) + .orElse(previousMailboxes); + } + + private List<Flags> patchFlags(List<MessageResult> messagesToBeUpdated, UpdateMessagePatch updateMessagePatch) { + return messagesToBeUpdated.stream() + .map(MessageResult::getFlags) + .map(updateMessagePatch::applyToStateNoReset) + .collect(Guavate.toImmutableList()); + } + + private List<MailboxId> mailboxIdFor(Role role, MailboxSession session) throws MailboxException { + return systemMailboxesProvider.getMailboxByRole(role, session) + .map(MessageManager::getId) + .collect(Guavate.toImmutableList()); + } + private Stream<MailboxException> updateFlags(MessageId messageId, UpdateMessagePatch updateMessagePatch, MailboxSession mailboxSession, MessageResult messageResult) { try { if (!updateMessagePatch.isFlagsIdentity()) { @@ -151,30 +215,10 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { .map(mailboxIdFactory::fromString) .collect(Guavate.toImmutableList()); - assertNotDraftMailbox(mailboxSession, mailboxIds); - assertNotDraftMessage(originalFlags); - messageIdManager.setInMailboxes(messageId, mailboxIds, mailboxSession); } } - private void assertNotDraftMessage(Stream<Flags> originalFlags) throws DraftMessageMailboxUpdateException { - boolean isADraftMessage = originalFlags - .anyMatch(flags -> flags.contains(Flags.Flag.DRAFT)); - if (isADraftMessage) { - throw new DraftMessageMailboxUpdateException(); - } - } - - private void assertNotDraftMailbox(MailboxSession mailboxSession, List<MailboxId> mailboxIds) throws MailboxException { - Stream<MessageManager> draftMailboxes = systemMailboxesProvider.getMailboxByRole(Role.DRAFTS, mailboxSession); - - boolean containsDraftMailboxes = draftMailboxes.map(MessageManager::getId).anyMatch(mailboxIds::contains); - if (containsDraftMailboxes) { - throw new DraftMessageMailboxUpdateException(); - } - } - private void addMessageIdNotFoundToResponse(MessageId messageId, SetMessagesResponse.Builder builder) { builder.notUpdated(ImmutableMap.of(messageId, SetError.builder() @@ -185,11 +229,12 @@ public class SetMessagesUpdateProcessor implements SetMessagesProcessor { } private SetMessagesResponse.Builder handleDraftMessageMailboxUpdateException(MessageId messageId, - SetMessagesResponse.Builder builder) { + SetMessagesResponse.Builder builder, + DraftMessageMailboxUpdateException e) { return builder.notUpdated(ImmutableMap.of(messageId, SetError.builder() .type("invalidArguments") .properties(MessageProperties.MessageProperty.mailboxIds) - .description("Draft messages can not be moved or copied out of the Draft mailbox") + .description(e.getMessage()) .build())); } http://git-wip-us.apache.org/repos/asf/james-project/blob/7c1685b4/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/UpdateMessagePatch.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/UpdateMessagePatch.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/UpdateMessagePatch.java index 49a2ac8..a49df67 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/UpdateMessagePatch.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/UpdateMessagePatch.java @@ -149,12 +149,13 @@ public class UpdateMessagePatch { } public Flags applyToState(Flags currentFlags) { - return keywords.map(keyword -> { - if (currentFlags.contains(Flags.Flag.DRAFT) != keyword.getKeywords().contains(Keyword.DRAFT)) { - throw new IllegalArgumentException("Cannot add or remove draft flag"); - } - return keyword.asFlagsWithRecentAndDeletedFrom(currentFlags); - }).orElse(new Flags()); + return keywords.map(keyword -> keyword.asFlagsWithRecentAndDeletedFrom(currentFlags)) + .orElse(new Flags()); + } + + public Flags applyToStateNoReset(Flags currentFlags) { + return keywords.map(keyword -> keyword.asFlagsWithRecentAndDeletedFrom(currentFlags)) + .orElse(currentFlags); } } --------------------------------------------------------------------- To unsubscribe, e-mail: server-dev-unsubscr...@james.apache.org For additional commands, e-mail: server-dev-h...@james.apache.org