JAMES-1716 Saving to draft should not send message providing both an outside integration test and some unit tests (SetMessageCreationProcessorTest)
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/d5988181 Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/d5988181 Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/d5988181 Branch: refs/heads/master Commit: d59881815c9c5fcee73addb2a2562c28f3491e16 Parents: fe2c10b Author: Matthieu Baechler <[email protected]> Authored: Mon Jul 4 16:36:05 2016 +0200 Committer: Antoine Duprat <[email protected]> Committed: Thu Jul 7 12:04:36 2016 +0200 ---------------------------------------------------------------------- .../store/mail/model/impl/SimpleMailbox.java | 9 +- .../java/org/apache/james/jmap/JMAPModule.java | 5 +- .../integration/SetMessagesMethodTest.java | 110 +++++++- .../java/org/apache/james/jmap/JMAPServlet.java | 4 + .../jmap/methods/MIMEMessageConverter.java | 14 +- .../MailboxInvalidMessageCreationException.java | 26 ++ .../methods/MailboxNotImplementedException.java | 30 ++ .../MailboxSendingNotAllowedException.java | 38 +++ .../james/jmap/methods/MessageWithId.java | 49 ---- .../methods/SetMessagesCreationProcessor.java | 275 ++++++++++--------- .../apache/james/jmap/methods/ValueWithId.java | 63 +++++ .../james/jmap/model/CreationMessage.java | 17 +- .../org/apache/james/jmap/model/Message.java | 8 +- .../org/apache/james/jmap/model/SetError.java | 14 + .../james/jmap/model/SetMessagesRequest.java | 30 +- .../james/jmap/model/SetMessagesResponse.java | 14 + .../james/jmap/send/PostDequeueDecorator.java | 2 +- .../exception/MailboxRoleNotFoundException.java | 30 -- .../jmap/utils/SystemMailboxesProvider.java | 30 ++ .../jmap/utils/SystemMailboxesProviderImpl.java | 70 +++++ .../jmap/methods/MIMEMessageConverterTest.java | 25 +- .../SetMessagesCreationProcessorTest.java | 268 +++++++++++------- .../apache/james/jmap/model/SetErrorTest.java | 28 +- .../jmap/model/SetMessagesRequestTest.java | 2 +- .../jmap/send/PostDequeueDecoratorTest.java | 2 +- .../utils/SystemMailboxesProviderImplTest.java | 33 +++ 26 files changed, 848 insertions(+), 348 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java ---------------------------------------------------------------------- diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java index 05c1ec1..7d70bfd 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/model/impl/SimpleMailbox.java @@ -33,13 +33,18 @@ public class SimpleMailbox implements Mailbox { private final long uidValidity; private MailboxACL acl = SimpleMailboxACL.EMPTY; - public SimpleMailbox(MailboxPath path, long uidValidity) { + public SimpleMailbox(MailboxPath path, long uidValidity, MailboxId mailboxId) { + this.id = mailboxId; this.namespace = path.getNamespace(); this.user = path.getUser(); this.name = path.getName(); this.uidValidity = uidValidity; } - + + public SimpleMailbox(MailboxPath path, long uidValidity) { + this(path, uidValidity, null); + } + public SimpleMailbox(Mailbox mailbox) { this.id = mailbox.getMailboxId(); this.namespace = mailbox.getNamespace(); http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java ---------------------------------------------------------------------- diff --git a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java index 6fd9c1f..247c70d 100644 --- a/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java +++ b/server/container/guice/guice-common/src/main/java/org/apache/james/jmap/JMAPModule.java @@ -30,6 +30,8 @@ import org.apache.james.jmap.mailet.VacationMailet; import org.apache.james.jmap.methods.RequestHandler; import org.apache.james.jmap.utils.HtmlTextExtractor; import org.apache.james.jmap.utils.MailboxBasedHtmlTextExtractor; +import org.apache.james.jmap.utils.SystemMailboxesProvider; +import org.apache.james.jmap.utils.SystemMailboxesProviderImpl; import org.apache.james.lifecycle.api.Configurable; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailetcontainer.impl.MatcherMailetPair; @@ -64,11 +66,12 @@ public class JMAPModule extends AbstractModule { bind(HtmlTextExtractor.class).to(MailboxBasedHtmlTextExtractor.class); Multibinder.newSetBinder(binder(), ConfigurationPerformer.class).addBinding().to(RequiredCapabilitiesPrecondition.class); - Multibinder.newSetBinder(binder(), ConfigurationPerformer.class).addBinding().to(RequiredCapabilitiesPrecondition.class); Multibinder<CamelMailetContainerModule.TransportProcessorCheck> transportProcessorChecks = Multibinder.newSetBinder(binder(), CamelMailetContainerModule.TransportProcessorCheck.class); transportProcessorChecks.addBinding().to(VacationMailetCheck.class); transportProcessorChecks.addBinding().to(BccMailetCheck.class); + + bind(SystemMailboxesProvider.class).to(SystemMailboxesProviderImpl.class); } @Provides http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java index ff6ded2..af96309 100644 --- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java +++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SetMessagesMethodTest.java @@ -23,6 +23,7 @@ import static com.jayway.restassured.RestAssured.given; import static com.jayway.restassured.RestAssured.with; import static com.jayway.restassured.config.EncoderConfig.encoderConfig; import static com.jayway.restassured.config.RestAssuredConfig.newConfig; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.endsWith; @@ -128,8 +129,6 @@ public abstract class SetMessagesMethodTest { .findFirst().get(); } - - private List<Map<String, String>> getAllMailboxesIds(AccessToken accessToken) { return with() .header("Authorization", accessToken.serialize()) @@ -1054,7 +1053,7 @@ public abstract class SetMessagesMethodTest { .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].type", equalTo("invalidProperties")) .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", hasSize(1)) .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].properties", contains("from")) - .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("Invalid 'from' field. Must be one of [[email protected]]")) + .body(ARGUMENTS + ".notCreated[\""+messageCreationId+"\"].description", endsWith("Invalid 'from' field. Must be one of [email protected]")) .body(ARGUMENTS + ".created", aMapWithSize(0)); } @@ -1282,10 +1281,10 @@ public abstract class SetMessagesMethodTest { .then() .statusCode(200) .body(NAME, equalTo("messageList")) - .body(ARGUMENTS + ".messageIds", hasSize(1)) - ; + .body(ARGUMENTS + ".messageIds", hasSize(1)); return true; - } catch(AssertionError e) { + + } catch (AssertionError e) { return false; } } @@ -1333,6 +1332,101 @@ public abstract class SetMessagesMethodTest { calmlyAwait.atMost(30, TimeUnit.SECONDS).until( () -> isHtmlMessageReceived(recipientToken)); } + + @Test + public void setMessagesWhenSavingToDraftsShouldNotSendMessage() throws Exception { + String sender = username; + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "sent"); + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "drafts"); + String recipientAddress = "recipient" + "@" + USERS_DOMAIN; + String recipientPassword = "password"; + jmapServer.serverProbe().addUser(recipientAddress, recipientPassword); + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, recipientAddress, "inbox"); + await(); + AccessToken recipientToken = JmapAuthentication.authenticateJamesUser(recipientAddress, recipientPassword); + + String senderDraftsMailboxId = getMailboxId(accessToken, Role.DRAFTS); + + String messageCreationId = "creationId"; + String fromAddress = username; + String requestBody = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"create\": { \"" + messageCreationId + "\" : {" + + " \"from\": { \"email\": \"" + fromAddress + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + + " \"cc\": [{ \"name\": \"ALICE\"}]," + + " \"subject\": \"Thank you for joining example.com!\"," + + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + + " \"mailboxIds\": [\"" + senderDraftsMailboxId + "\"], " + + " \"isDraft\": false" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + given() + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200); + + //We need to wait for an async event to not happen, we couldn't found any + //robust way to check that. + Thread.sleep(TimeUnit.SECONDS.toMillis(5)); + assertThat(isAnyMessageFoundInRecipientsMailboxes(recipientToken)).isFalse(); + } + + @Test + public void setMessagesWhenSavingToRegularMailboxShouldNotSendMessage() throws Exception { + String sender = username; + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "sent"); + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "drafts"); + jmapServer.serverProbe().createMailbox(MailboxConstants.USER_NAMESPACE, sender, "regular"); + Mailbox regularMailbox = jmapServer.serverProbe().getMailbox(MailboxConstants.USER_NAMESPACE, sender, "regular"); + String recipientAddress = "recipient" + "@" + USERS_DOMAIN; + String recipientPassword = "password"; + jmapServer.serverProbe().addUser(recipientAddress, recipientPassword); + await(); + + String messageCreationId = "creationId"; + String fromAddress = username; + String requestBody = "[" + + " [" + + " \"setMessages\","+ + " {" + + " \"create\": { \"" + messageCreationId + "\" : {" + + " \"from\": { \"email\": \"" + fromAddress + "\"}," + + " \"to\": [{ \"name\": \"BOB\", \"email\": \"" + recipientAddress + "\"}]," + + " \"cc\": [{ \"name\": \"ALICE\"}]," + + " \"subject\": \"Thank you for joining example.com!\"," + + " \"textBody\": \"Hello someone, and thank you for joining example.com!\"," + + " \"mailboxIds\": [\"" + regularMailbox.getMailboxId().serialize() + "\"]" + + " }}" + + " }," + + " \"#0\"" + + " ]" + + "]"; + + String notCreatedMessage = ARGUMENTS + ".notCreated[\""+messageCreationId+"\"]"; + given() + .header("Authorization", this.accessToken.serialize()) + .body(requestBody) + .when() + .post("/jmap") + .then() + .statusCode(200) + .body(ARGUMENTS + ".notCreated", hasKey(messageCreationId)) + .body(notCreatedMessage + ".type", equalTo("invalidProperties")) + .body(notCreatedMessage + ".description", equalTo("Not yet implemented")) + .body(ARGUMENTS + ".created", aMapWithSize(0)); + } + + private boolean isHtmlMessageReceived(AccessToken recipientToken) { try { with() @@ -1375,7 +1469,7 @@ public abstract class SetMessagesMethodTest { " \"subject\": \"Thank you for joining example.com!\"," + " \"htmlBody\": \"Hello <b>someone</b>, and thank you for joining example.com!\"," + " \"textBody\": \"Hello someone, and thank you for joining example.com, text version!\"," + - " \"mailboxIds\": [\"" + getOutboxId() + "\"]" + + " \"mailboxIds\": [\"" + getOutboxId(accessToken) + "\"]" + " }}" + " }," + " \"#0\"" + @@ -1439,8 +1533,6 @@ public abstract class SetMessagesMethodTest { "]"; given() - .accept(ContentType.JSON) - .contentType(ContentType.JSON) .header("Authorization", this.accessToken.serialize()) .body(requestBody) .when() http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java index 17827d4..832a940 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPServlet.java @@ -36,6 +36,8 @@ import org.apache.james.jmap.methods.RequestHandler; import org.apache.james.jmap.model.AuthenticatedProtocolRequest; import org.apache.james.jmap.model.ProtocolRequest; import org.apache.james.jmap.model.ProtocolResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; @@ -44,6 +46,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; public class JMAPServlet extends HttpServlet { + public static final Logger LOG = LoggerFactory.getLogger(JMAPServlet.class); public static final String JSON_CONTENT_TYPE = "application/json"; public static final String JSON_CONTENT_TYPE_UTF8 = "application/json; charset=UTF-8"; @@ -70,6 +73,7 @@ public class JMAPServlet extends HttpServlet { resp.setContentType(JSON_CONTENT_TYPE); objectMapper.writeValue(resp.getOutputStream(), responses); } catch (IOException e) { + LOG.error("error handling request", e); resp.setStatus(SC_BAD_REQUEST); } } http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java index 19e7c63..25ba906 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MIMEMessageConverter.java @@ -68,7 +68,7 @@ public class MIMEMessageConverter { this.bodyFactory = new BasicBodyFactory(); } - public byte[] convert(MessageWithId.CreationMessageEntry creationMessageEntry) { + public byte[] convert(ValueWithId.CreationMessageEntry creationMessageEntry) { ByteArrayOutputStream buffer = new ByteArrayOutputStream(); DefaultMessageWriter writer = new DefaultMessageWriter(); @@ -80,18 +80,18 @@ public class MIMEMessageConverter { return buffer.toByteArray(); } - @VisibleForTesting Message convertToMime(MessageWithId.CreationMessageEntry creationMessageEntry) { - if (creationMessageEntry == null || creationMessageEntry.getMessage() == null) { + @VisibleForTesting Message convertToMime(ValueWithId.CreationMessageEntry creationMessageEntry) { + if (creationMessageEntry == null || creationMessageEntry.getValue() == null) { throw new IllegalArgumentException("creationMessageEntry is either null or has null message"); } MessageBuilder messageBuilder = MessageBuilder.create(); - if (mixedTextAndHtml(creationMessageEntry.getMessage())) { - messageBuilder.setBody(createMultipartBody(creationMessageEntry.getMessage())); + if (mixedTextAndHtml(creationMessageEntry.getValue())) { + messageBuilder.setBody(createMultipartBody(creationMessageEntry.getValue())); } else { - messageBuilder.setBody(createTextBody(creationMessageEntry.getMessage())); + messageBuilder.setBody(createTextBody(creationMessageEntry.getValue())); } - buildMimeHeaders(messageBuilder, creationMessageEntry.getCreationId(), creationMessageEntry.getMessage()); + buildMimeHeaders(messageBuilder, creationMessageEntry.getCreationId(), creationMessageEntry.getValue()); return messageBuilder.build(); } http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxInvalidMessageCreationException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxInvalidMessageCreationException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxInvalidMessageCreationException.java new file mode 100644 index 0000000..3bcb1be --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxInvalidMessageCreationException.java @@ -0,0 +1,26 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import org.apache.james.mailbox.exception.MailboxException; + +public class MailboxInvalidMessageCreationException extends MailboxException { + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxNotImplementedException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxNotImplementedException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxNotImplementedException.java new file mode 100644 index 0000000..a7da6d4 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxNotImplementedException.java @@ -0,0 +1,30 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import org.apache.james.mailbox.exception.MailboxException; + +public class MailboxNotImplementedException extends MailboxException { + + public MailboxNotImplementedException(String message) { + super(message); + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxSendingNotAllowedException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxSendingNotAllowedException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxSendingNotAllowedException.java new file mode 100644 index 0000000..49d9141 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MailboxSendingNotAllowedException.java @@ -0,0 +1,38 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import java.util.Collection; + +import org.apache.james.mailbox.exception.MailboxException; + +public class MailboxSendingNotAllowedException extends MailboxException { + + private Collection<String> allowedFroms; + + public MailboxSendingNotAllowedException(Collection<String> allowedFroms) { + super(); + this.allowedFroms = allowedFroms; + } + + public Collection<String> getAllowedFroms() { + return allowedFroms; + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java deleted file mode 100644 index 0e4e172..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageWithId.java +++ /dev/null @@ -1,49 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.jmap.methods; - -import org.apache.james.jmap.model.CreationMessage; -import org.apache.james.jmap.model.CreationMessageId; - -public class MessageWithId<T> { - - private CreationMessageId creationId; - private T message; - - public MessageWithId(CreationMessageId creationId, T message) { - this.creationId = creationId; - this.message = message; - } - - public CreationMessageId getCreationId() { - return creationId; - } - - public T getMessage() { - return message; - } - - public static class CreationMessageEntry extends MessageWithId<CreationMessage> { - public CreationMessageEntry(CreationMessageId creationId, CreationMessage message) { - super(creationId, message); - } - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java index 49f87a9..3da428b 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SetMessagesCreationProcessor.java @@ -20,14 +20,11 @@ package org.apache.james.jmap.methods; import java.io.IOException; -import java.util.AbstractMap; import java.util.Date; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.function.Predicate; import java.util.stream.Collectors; import javax.inject.Inject; @@ -36,7 +33,8 @@ import javax.mail.MessagingException; import javax.mail.internet.SharedInputStream; import javax.mail.util.SharedByteArrayInputStream; -import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException; +import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry; +import org.apache.james.jmap.methods.ValueWithId.MessageWithId; import org.apache.james.jmap.model.CreationMessage; import org.apache.james.jmap.model.CreationMessageId; import org.apache.james.jmap.model.Message; @@ -47,18 +45,17 @@ import org.apache.james.jmap.model.MessageProperties.MessageProperty; import org.apache.james.jmap.model.SetError; import org.apache.james.jmap.model.SetMessagesRequest; import org.apache.james.jmap.model.SetMessagesResponse; +import org.apache.james.jmap.model.SetMessagesResponse.Builder; import org.apache.james.jmap.model.mailbox.Role; import org.apache.james.jmap.send.MailFactory; import org.apache.james.jmap.send.MailMetadata; import org.apache.james.jmap.send.MailSpool; -import org.apache.james.mailbox.MailboxManager; +import org.apache.james.jmap.utils.SystemMailboxesProvider; import org.apache.james.mailbox.MailboxSession; import org.apache.james.mailbox.exception.MailboxException; -import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.model.MailboxPath; -import org.apache.james.mailbox.model.MailboxQuery; import org.apache.james.mailbox.store.MailboxSessionMapperFactory; -import org.apache.james.mailbox.store.mail.MailboxMapperFactory; import org.apache.james.mailbox.store.mail.MessageMapper; import org.apache.james.mailbox.store.mail.model.Mailbox; import org.apache.james.mailbox.store.mail.model.MailboxId; @@ -69,150 +66,186 @@ import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.github.fge.lambdas.functions.ThrowingFunction; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Joiner; import com.google.common.base.Splitter; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Lists; public class SetMessagesCreationProcessor implements SetMessagesProcessor { - private static final Logger LOGGER = LoggerFactory.getLogger(SetMessagesCreationProcessor.class); - - private final MailboxMapperFactory mailboxMapperFactory; - private final MailboxManager mailboxManager; + private static final Logger LOG = LoggerFactory.getLogger(SetMailboxesCreationProcessor.class); private final MailboxSessionMapperFactory mailboxSessionMapperFactory; private final MIMEMessageConverter mimeMessageConverter; private final MailSpool mailSpool; private final MailFactory mailFactory; private final MessageFactory messageFactory; + private final SystemMailboxesProvider systemMailboxesProvider; - @Inject - @VisibleForTesting - SetMessagesCreationProcessor(MailboxMapperFactory mailboxMapperFactory, - MailboxManager mailboxManager, - MailboxSessionMapperFactory mailboxSessionMapperFactory, + + @VisibleForTesting @Inject + SetMessagesCreationProcessor(MailboxSessionMapperFactory mailboxSessionMapperFactory, MIMEMessageConverter mimeMessageConverter, MailSpool mailSpool, MailFactory mailFactory, - MessageFactory messageFactory) { - this.mailboxMapperFactory = mailboxMapperFactory; - this.mailboxManager = mailboxManager; + MessageFactory messageFactory, + SystemMailboxesProvider systemMailboxesProvider) { this.mailboxSessionMapperFactory = mailboxSessionMapperFactory; this.mimeMessageConverter = mimeMessageConverter; this.mailSpool = mailSpool; this.mailFactory = mailFactory; this.messageFactory = messageFactory; + this.systemMailboxesProvider = systemMailboxesProvider; } @Override public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) { - Mailbox outbox; + Builder responseBuilder = SetMessagesResponse.builder(); + request.getCreate() + .stream() + .forEach(create -> handleCreate(create, responseBuilder, mailboxSession)); + return responseBuilder.build(); + } + + private void handleCreate(CreationMessageEntry create, Builder responseBuilder, MailboxSession mailboxSession) { try { - outbox = getOutbox(mailboxSession).orElseThrow(() -> new MailboxRoleNotFoundException(Role.OUTBOX)); - } catch (MailboxException | MailboxRoleNotFoundException e) { - LOGGER.error("Unable to find a mailbox with role 'outbox'!"); - throw Throwables.propagate(e); - } + validateImplementedFeature(create, mailboxSession); + validateArguments(create); + validateRights(create, mailboxSession); + MessageWithId created = handleOutboxMessages(create, mailboxSession); + responseBuilder.created(created.getCreationId(), created.getValue()); - List<String> allowedSenders = ImmutableList.of(mailboxSession.getUser().getUserName()); + } catch (MailboxSendingNotAllowedException e) { + responseBuilder.notCreated(create.getCreationId(), + SetError.builder() + .type("invalidProperties") + .properties(MessageProperty.from) + .description("Invalid 'from' field. Must be one of " + + Joiner.on(", ").join(e.getAllowedFroms())) + .build()); - // handle errors - Predicate<CreationMessage> validMessagesTester = creationMessage -> creationMessage.isValid() && isAllowedFromAddress(creationMessage, allowedSenders); - Predicate<CreationMessage> invalidMessagesTester = validMessagesTester.negate(); - Function<CreationMessage, List<ValidationResult>> toValidationResults = creationMessage -> ImmutableList.<ValidationResult>builder() - .addAll(creationMessage.validate()) - .addAll(validationResultForIncorrectAddress(creationMessage, allowedSenders)) - .build(); + } catch (MailboxNotImplementedException e) { + responseBuilder.notCreated(create.getCreationId(), + SetError.builder() + .type("invalidProperties") + .properties(MessageProperty.mailboxIds) + .description("Not yet implemented") + .build()); - SetMessagesResponse.Builder responseBuilder = SetMessagesResponse.builder() - .notCreated(handleCreationErrors(invalidMessagesTester, toValidationResults, request)); + } catch (MailboxInvalidMessageCreationException e) { + responseBuilder.notCreated(create.getCreationId(), + buildSetErrorFromValidationResult(create.getValue().validate())); - return request.getCreate().entrySet().stream() - .filter(e -> validMessagesTester.test(e.getValue())) - .map(e -> new MessageWithId.CreationMessageEntry(e.getKey(), e.getValue())) - .map(nuMsg -> createMessageInOutboxAndSend(nuMsg, mailboxSession, outbox, buildMessageIdFunc(mailboxSession, outbox))) - .map(msg -> SetMessagesResponse.builder().created(ImmutableMap.of(msg.getCreationId(), msg.getMessage())).build()) - .reduce(responseBuilder, SetMessagesResponse.Builder::accumulator, SetMessagesResponse.Builder::combiner) - .build(); - } + } catch (MailboxNotFoundException e) { + responseBuilder.notCreated(create.getCreationId(), + SetError.builder() + .type("error") + .description(e.getMailboxName() + " can't be found") + .build()); - private boolean isAllowedFromAddress(CreationMessage creationMessage, List<String> allowedFromMailAddresses) { - return creationMessage.getFrom() - .map(draftEmailer -> draftEmailer.getEmail() - .map(allowedFromMailAddresses::contains) - .orElse(false)) - .orElse(false); + } catch (MailboxException | MessagingException e) { + LOG.error("Unexpected error while creating message", e); + responseBuilder.notCreated(create.getCreationId(), + SetError.builder() + .type("error") + .description("unexpected error") + .build()); + } } - - private List<ValidationResult> validationResultForIncorrectAddress(CreationMessage creationMessage, List<String> allowedSenders) { + + private void validateImplementedFeature(CreationMessageEntry entry, MailboxSession session) throws MailboxNotImplementedException { + if (isAppendToMailboxWithRole(Role.DRAFTS, entry.getValue(), session)) { + throw new MailboxNotImplementedException("Drafts saving is not implemented"); + } + if (!isAppendToMailboxWithRole(Role.OUTBOX, entry.getValue(), session)) { + throw new MailboxNotImplementedException("The only implemented feature is sending via outbox"); + } + } + + private void validateArguments(CreationMessageEntry entry) throws MailboxInvalidMessageCreationException { + CreationMessage message = entry.getValue(); + if (!message.isValid()) { + throw new MailboxInvalidMessageCreationException(); + } + } + + private void validateRights(CreationMessageEntry entry, MailboxSession session) throws MailboxSendingNotAllowedException { + List<String> allowedSenders = ImmutableList.of(session.getUser().getUserName()); + if (!isAllowedFromAddress(entry.getValue(), allowedSenders)) { + throw new MailboxSendingNotAllowedException(allowedSenders); + } + } + + private boolean isAllowedFromAddress(CreationMessage creationMessage, List<String> allowedFromMailAddresses) { return creationMessage.getFrom() - .map(draftEmailer -> draftEmailer - .getEmail() - .map(mail -> validationResultForIncorrectAddress(allowedSenders, mail)) - .orElse(Lists.newArrayList())) - .orElse(Lists.newArrayList()); + .map(draftEmailer -> draftEmailer.getEmail() + .map(allowedFromMailAddresses::contains) + .orElse(false)) + .orElse(false); } - private List<ValidationResult> validationResultForIncorrectAddress(List<String> allowedSenders, String mail) { - if (!allowedSenders.contains(mail)) { - return Lists.newArrayList(ValidationResult.builder() - .message("Invalid 'from' field. Must be one of " + allowedSenders) - .property(MessageProperty.from.asFieldName()) - .build()); + + private MessageWithId handleOutboxMessages(CreationMessageEntry entry, MailboxSession session) throws MailboxException, MessagingException { + Mailbox outbox = getMailboxWithRole(session, Role.OUTBOX).orElseThrow(() -> new MailboxNotFoundException(Role.OUTBOX.serialize())); + if (!isRequestForSending(entry.getValue(), session)) { + throw new IllegalStateException("Messages for everything but outbox should have been filtered earlier"); } - return Lists.newArrayList(); + Function<Long, MessageId> idGenerator = uid -> generateMessageId(session, outbox, uid); + return createMessageInOutboxAndSend(entry, session, outbox, idGenerator); } - - private Map<CreationMessageId, SetError> handleCreationErrors(Predicate<CreationMessage> invalidMessagesTester, - Function<CreationMessage, List<ValidationResult>> toValidationResults, - SetMessagesRequest request) { - return request.getCreate().entrySet().stream() - .filter(e -> invalidMessagesTester.test(e.getValue())) - .map(e -> new AbstractMap.SimpleEntry<>(e.getKey(), buildSetErrorFromValidationResult(toValidationResults.apply(e.getValue())))) - .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); + + @VisibleForTesting + protected MessageWithId createMessageInOutboxAndSend(CreationMessageEntry createdEntry, + MailboxSession session, + Mailbox outbox, Function<Long, MessageId> buildMessageIdFromUid) throws MailboxException, MessagingException { + + CreationMessageId creationId = createdEntry.getCreationId(); + MessageMapper messageMapper = mailboxSessionMapperFactory.createMessageMapper(session); + MailboxMessage newMailboxMessage = buildMailboxMessage(createdEntry, outbox); + messageMapper.add(outbox, newMailboxMessage); + Message jmapMessage = messageFactory.fromMailboxMessage(newMailboxMessage, ImmutableList.of(), buildMessageIdFromUid); + sendMessage(newMailboxMessage, jmapMessage, session); + return new MessageWithId(creationId, jmapMessage); + } + + private boolean isAppendToMailboxWithRole(Role role, CreationMessage entry, MailboxSession mailboxSession) { + return getMailboxWithRole(mailboxSession, role) + .map(box -> entry.isIn(box)) + .orElse(false); } + private Optional<Mailbox> getMailboxWithRole(MailboxSession mailboxSession, Role role) { + return systemMailboxesProvider.listMailboxes(role, mailboxSession).findFirst(); + } + private SetError buildSetErrorFromValidationResult(List<ValidationResult> validationErrors) { - String formattedValidationErrorMessage = validationErrors.stream() + return SetError.builder() + .type("invalidProperties") + .properties(collectMessageProperties(validationErrors)) + .description(formatValidationErrorMessge(validationErrors)) + .build(); + } + + private String formatValidationErrorMessge(List<ValidationResult> validationErrors) { + return validationErrors.stream() .map(err -> err.getProperty() + ": " + err.getErrorMessage()) .collect(Collectors.joining("\\n")); + } + + private Set<MessageProperties.MessageProperty> collectMessageProperties(List<ValidationResult> validationErrors) { Splitter propertiesSplitter = Splitter.on(',').trimResults().omitEmptyStrings(); - Set<MessageProperties.MessageProperty> properties = validationErrors.stream() + return validationErrors.stream() .flatMap(err -> propertiesSplitter.splitToList(err.getProperty()).stream()) .flatMap(MessageProperty::find) .collect(Collectors.toSet()); - return SetError.builder() - .type("invalidProperties") - .properties(properties) - .description(formattedValidationErrorMessage) - .build(); } - @VisibleForTesting - protected MessageWithId<Message> createMessageInOutboxAndSend(MessageWithId.CreationMessageEntry createdEntry, - MailboxSession session, - Mailbox outbox, Function<Long, MessageId> buildMessageIdFromUid) { - try { - MessageMapper messageMapper = mailboxSessionMapperFactory.createMessageMapper(session); - MailboxMessage newMailboxMessage = buildMailboxMessage(createdEntry, outbox); - messageMapper.add(outbox, newMailboxMessage); - Message jmapMessage = messageFactory.fromMailboxMessage(newMailboxMessage, ImmutableList.of(), buildMessageIdFromUid); - sendMessage(newMailboxMessage, jmapMessage, session); - return new MessageWithId<>(createdEntry.getCreationId(), jmapMessage); - } catch (MailboxException | MessagingException | IOException e) { - throw Throwables.propagate(e); - } catch (MailboxRoleNotFoundException e) { - LOGGER.error("Could not find mailbox '%s' while trying to save message.", e.getRole().serialize()); - throw Throwables.propagate(e); - } + private boolean isRequestForSending(CreationMessage creationMessage, MailboxSession session) { + return isAppendToMailboxWithRole(Role.OUTBOX, creationMessage, session); } - - private Function<Long, MessageId> buildMessageIdFunc(MailboxSession session, Mailbox outbox) { + + private MessageId generateMessageId(MailboxSession session, Mailbox outbox, long uid) { MailboxPath outboxPath = new MailboxPath(session.getPersonalSpace(), session.getUser().getUserName(), outbox.getName()); - return uid -> new MessageId(session.getUser(), outboxPath, uid); + return new MessageId(session.getUser(), outboxPath, uid); } private MailboxMessage buildMailboxMessage(MessageWithId.CreationMessageEntry createdEntry, Mailbox outbox) { @@ -221,35 +254,15 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { long size = messageContent.length; int bodyStartOctet = 0; - Flags flags = getMessageFlags(createdEntry.getMessage()); + Flags flags = getMessageFlags(createdEntry.getValue()); PropertyBuilder propertyBuilder = buildPropertyBuilder(); MailboxId mailboxId = outbox.getMailboxId(); - Date internalDate = Date.from(createdEntry.getMessage().getDate().toInstant()); + Date internalDate = Date.from(createdEntry.getValue().getDate().toInstant()); return new SimpleMailboxMessage(internalDate, size, bodyStartOctet, content, flags, propertyBuilder, mailboxId); } - @VisibleForTesting - protected Optional<Mailbox> getOutbox(MailboxSession session) throws MailboxException { - return mailboxManager.search(MailboxQuery.builder(session) - .privateUserMailboxes().build(), session).stream() - .map(MailboxMetaData::getPath) - .filter(this::hasRoleOutbox) - .map(loadMailbox(session)) - .findFirst(); - } - - private boolean hasRoleOutbox(MailboxPath mailBoxPath) { - return Role.from(mailBoxPath.getName()) - .map(Role.OUTBOX::equals) - .orElse(false); - } - - private ThrowingFunction<MailboxPath, Mailbox> loadMailbox(MailboxSession session) { - return path -> mailboxMapperFactory.getMailboxMapper(session).findMailboxByPath(path); - } - private PropertyBuilder buildPropertyBuilder() { return new PropertyBuilder(); } @@ -271,9 +284,17 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { return result; } - private void sendMessage(MailboxMessage mailboxMessage, Message jmapMessage, MailboxSession session) throws MessagingException, IOException { - Mail mail = mailFactory.build(mailboxMessage, jmapMessage); + private void sendMessage(MailboxMessage mailboxMessage, Message jmapMessage, MailboxSession session) throws MessagingException { + Mail mail = buildMessage(mailboxMessage, jmapMessage); MailMetadata metadata = new MailMetadata(jmapMessage.getId(), session.getUser().getUserName()); mailSpool.send(mail, metadata); } + + private Mail buildMessage(MailboxMessage mailboxMessage, Message jmapMessage) throws MessagingException { + try { + return mailFactory.build(mailboxMessage, jmapMessage); + } catch (IOException e) { + throw new MessagingException("error building message to send", e); + } + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValueWithId.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValueWithId.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValueWithId.java new file mode 100644 index 0000000..a5548c1 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/ValueWithId.java @@ -0,0 +1,63 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.methods; + +import org.apache.james.jmap.model.CreationMessage; +import org.apache.james.jmap.model.CreationMessageId; +import org.apache.james.jmap.model.SetError; +import org.apache.james.jmap.model.Message; + +public class ValueWithId<T> { + + private CreationMessageId creationId; + private T value; + + private ValueWithId(CreationMessageId creationId, T value) { + this.creationId = creationId; + this.value = value; + } + + public CreationMessageId getCreationId() { + return creationId; + } + + public T getValue() { + return value; + } + + public static class CreationMessageEntry extends ValueWithId<CreationMessage> { + public CreationMessageEntry(CreationMessageId creationId, CreationMessage message) { + super(creationId, message); + } + } + + public static class ErrorWithId extends ValueWithId<SetError> { + public ErrorWithId(CreationMessageId creationId, SetError error) { + super(creationId, error); + } + } + + public static class MessageWithId extends ValueWithId<Message> { + public MessageWithId(CreationMessageId creationId, Message message) { + super(creationId, message); + } + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java index de2453d..60f7b91 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/CreationMessage.java @@ -19,9 +19,8 @@ package org.apache.james.jmap.model; -import static org.apache.james.jmap.model.MessageProperties.MessageProperty; - import java.time.ZonedDateTime; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; @@ -32,6 +31,8 @@ import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import org.apache.james.jmap.methods.ValidationResult; +import org.apache.james.jmap.model.MessageProperties.MessageProperty; +import org.apache.james.mailbox.store.mail.model.Mailbox; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -84,8 +85,12 @@ public class CreationMessage { headers = ImmutableMap.builder(); } - public Builder mailboxIds(ImmutableList<String> mailboxIds) { - this.mailboxIds = mailboxIds; + public Builder mailboxId(String... mailboxIds) { + return mailboxIds(Arrays.asList(mailboxIds)); + } + + public Builder mailboxIds(List<String> mailboxIds) { + this.mailboxIds = ImmutableList.copyOf(mailboxIds); return this; } @@ -349,6 +354,10 @@ public class CreationMessage { from.filter(f -> !f.hasValidEmail()).ifPresent(f -> errors.add(invalidPropertyFrom)); } + public boolean isIn(Mailbox mailbox) { + return mailboxIds.contains(mailbox.getMailboxId().serialize()); + } + @JsonDeserialize(builder = DraftEmailer.Builder.class) public static class DraftEmailer { http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java index 1c1cd50..94b8517 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Message.java @@ -95,8 +95,12 @@ public class Message { return this; } - public Builder mailboxIds(ImmutableList<String> mailboxIds) { - this.mailboxIds = mailboxIds; + public Builder mailboxId(String mailboxId) { + return this.mailboxIds(ImmutableList.of(mailboxId)); + } + + public Builder mailboxIds(List<String> mailboxIds) { + this.mailboxIds = ImmutableList.copyOf(mailboxIds); return this; } http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java index 75aacd2..658491c 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetError.java @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.MoreObjects; import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -61,6 +62,10 @@ public class SetError { return this; } + public Builder properties(MessageProperty... properties) { + return properties(ImmutableSet.copyOf(properties)); + } + public Builder properties(Set<MessageProperty> properties) { this.properties = Optional.of(Sets.union( this.properties.orElse(ImmutableSet.of()), @@ -116,4 +121,13 @@ public class SetError { public int hashCode() { return Objects.hashCode(type, description, properties); } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("description", description) + .add("type", type) + .add("properties", properties) + .toString(); + } } http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java index 096e9c6..a064102 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesRequest.java @@ -19,13 +19,17 @@ package org.apache.james.jmap.model; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.function.Function; +import org.apache.commons.lang.NotImplementedException; import org.apache.james.jmap.methods.JmapRequest; import org.apache.james.jmap.methods.UpdateMessagePatchConverter; +import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry; +import org.apache.james.util.streams.ImmutableCollectors; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; @@ -34,7 +38,6 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; -import org.apache.commons.lang.NotImplementedException; @JsonDeserialize(builder = SetMessagesRequest.Builder.class) public class SetMessagesRequest implements JmapRequest { @@ -48,13 +51,13 @@ public class SetMessagesRequest implements JmapRequest { private String accountId; private String ifInState; - private ImmutableMap.Builder<CreationMessageId, CreationMessage> create; + private HashMap<CreationMessageId, CreationMessage> create; private ImmutableMap.Builder<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> updatesProvider; private ImmutableList.Builder<MessageId> destroy; private Builder() { - create = ImmutableMap.builder(); + create = new HashMap<>(); updatesProvider = ImmutableMap.builder(); destroy = ImmutableList.builder(); } @@ -73,6 +76,11 @@ public class SetMessagesRequest implements JmapRequest { return this; } + public Builder create(CreationMessageId creationMessageId, CreationMessage creation) { + this.create.put(creationMessageId, creation); + return this; + } + public Builder create(Map<CreationMessageId, CreationMessage> creations) { this.create.putAll(creations); return this; @@ -89,17 +97,24 @@ public class SetMessagesRequest implements JmapRequest { } public SetMessagesRequest build() { - return new SetMessagesRequest(Optional.ofNullable(accountId), Optional.ofNullable(ifInState), create.build(), updatesProvider.build(), destroy.build()); + return new SetMessagesRequest(Optional.ofNullable(accountId), Optional.ofNullable(ifInState), + messageCreations(), updatesProvider.build(), destroy.build()); + } + + private ImmutableList<CreationMessageEntry> messageCreations() { + return create.entrySet().stream() + .map(entry -> new CreationMessageEntry(entry.getKey(), entry.getValue())) + .collect(ImmutableCollectors.toImmutableList()); } } private final Optional<String> accountId; private final Optional<String> ifInState; - private final Map<CreationMessageId, CreationMessage> create; + private final List<CreationMessageEntry> create; private final Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update; private final List<MessageId> destroy; - @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, Map<CreationMessageId, CreationMessage> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update, List<MessageId> destroy) { + @VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState, List<CreationMessageEntry> create, Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update, List<MessageId> destroy) { this.accountId = accountId; this.ifInState = ifInState; this.create = create; @@ -115,7 +130,7 @@ public class SetMessagesRequest implements JmapRequest { return ifInState; } - public Map<CreationMessageId, CreationMessage> getCreate() { + public List<CreationMessageEntry> getCreate() { return create; } @@ -126,4 +141,5 @@ public class SetMessagesRequest implements JmapRequest { public List<MessageId> getDestroy() { return destroy; } + } http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java index a536ba4..96286e7 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/SetMessagesResponse.java @@ -81,6 +81,11 @@ public class SetMessagesResponse implements Method.Response { throw new NotImplementedException(); } + public Builder created(CreationMessageId creationMessageId, Message message) { + this.created.put(creationMessageId, message); + return this; + } + public Builder created(Map<CreationMessageId, Message> created) { this.created.putAll(created); return this; @@ -105,6 +110,11 @@ public class SetMessagesResponse implements Method.Response { this.notCreated.putAll(notCreated); return this; } + + public Builder notCreated(CreationMessageId id, SetError error) { + this.notCreated.put(id, error); + return this; + } public Builder notUpdated(Map<MessageId, SetError> notUpdated) { this.notUpdated.putAll(notUpdated); @@ -121,6 +131,10 @@ public class SetMessagesResponse implements Method.Response { return this; } + public Builder mergeWith(Builder otherBuilder) { + return otherBuilder.build().mergeInto(this); + } + public SetMessagesResponse build() { return new SetMessagesResponse(accountId, oldState, newState, created.build(), updated.build(), destroyed.build(), notCreated.build(), notUpdated.build(), notDestroyed.build()); http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/PostDequeueDecorator.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/PostDequeueDecorator.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/PostDequeueDecorator.java index 6d7f083..52c2dfd 100644 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/PostDequeueDecorator.java +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/PostDequeueDecorator.java @@ -21,10 +21,10 @@ package org.apache.james.jmap.send; import java.io.Serializable; import java.util.Iterator; +import org.apache.james.jmap.exceptions.MailboxRoleNotFoundException; import org.apache.james.jmap.model.MessageId; import org.apache.james.jmap.model.mailbox.Role; import org.apache.james.jmap.send.exception.MailShouldBeInOutboxException; -import org.apache.james.jmap.send.exception.MailboxRoleNotFoundException; import org.apache.james.jmap.send.exception.MessageIdNotFoundException; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/exception/MailboxRoleNotFoundException.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/exception/MailboxRoleNotFoundException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/exception/MailboxRoleNotFoundException.java deleted file mode 100644 index 574edf1..0000000 --- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/send/exception/MailboxRoleNotFoundException.java +++ /dev/null @@ -1,30 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ -package org.apache.james.jmap.send.exception; - -import org.apache.james.jmap.model.mailbox.Role; -import org.apache.james.queue.api.MailQueue.MailQueueException; - -public class MailboxRoleNotFoundException extends MailQueueException { - - public MailboxRoleNotFoundException(Role role) { - super("Unable to find a mailbox with role " + role.serialize()); - } - -} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProvider.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProvider.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProvider.java new file mode 100644 index 0000000..908ef9c --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProvider.java @@ -0,0 +1,30 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.utils; + +import java.util.stream.Stream; + +import org.apache.james.jmap.model.mailbox.Role; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +public interface SystemMailboxesProvider { + Stream<Mailbox> listMailboxes(Role aRole, MailboxSession session); +} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProviderImpl.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProviderImpl.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProviderImpl.java new file mode 100644 index 0000000..b868c85 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/utils/SystemMailboxesProviderImpl.java @@ -0,0 +1,70 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.jmap.utils; + +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Stream; + +import javax.inject.Inject; + +import org.apache.james.jmap.model.mailbox.Role; +import org.apache.james.mailbox.MailboxManager; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.model.MailboxMetaData; +import org.apache.james.mailbox.model.MailboxPath; +import org.apache.james.mailbox.model.MailboxQuery; +import org.apache.james.mailbox.store.mail.MailboxMapperFactory; +import org.apache.james.mailbox.store.mail.model.Mailbox; + +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.github.fge.lambdas.supplier.ThrowingSupplier; +import com.google.common.annotations.VisibleForTesting; + +public class SystemMailboxesProviderImpl implements SystemMailboxesProvider { + + private final MailboxMapperFactory mailboxMapperFactory; + private final MailboxManager mailboxManager; + + @Inject + @VisibleForTesting SystemMailboxesProviderImpl(MailboxMapperFactory mailboxMapperFactory, MailboxManager mailboxManager) { + this.mailboxMapperFactory = mailboxMapperFactory; + this.mailboxManager = mailboxManager; + } + + private boolean hasRole(Role aRole, MailboxPath mailBoxPath) { + return Role.from(mailBoxPath.getName()) + .map(aRole::equals) + .orElse(false); + } + + public Stream<Mailbox> listMailboxes(Role aRole, MailboxSession session) { + ThrowingSupplier<List<MailboxMetaData>> getAllMailboxes = () -> mailboxManager.search(MailboxQuery.builder(session).privateUserMailboxes().build(), session); + Predicate<MailboxPath> hasSpecifiedRole = path -> hasRole(aRole, path); + return getAllMailboxes.get().stream() + .map(MailboxMetaData::getPath) + .filter(hasSpecifiedRole) + .map(loadMailbox(session)); + } + + private ThrowingFunction<MailboxPath, Mailbox> loadMailbox(MailboxSession session) { + return path -> mailboxMapperFactory.getMailboxMapper(session).findMailboxByPath(path); + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/d5988181/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java index c2cf064..e64d6a9 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/MIMEMessageConverterTest.java @@ -26,6 +26,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; +import org.apache.james.jmap.methods.ValueWithId.MessageWithId; import org.apache.james.jmap.model.CreationMessage; import org.apache.james.jmap.model.CreationMessage.DraftEmailer; import org.apache.james.jmap.model.CreationMessageId; @@ -55,7 +56,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), messageHavingInReplyTo)); // Then @@ -67,7 +68,7 @@ public class MIMEMessageConverterTest { public void convertToMimeShouldThrowWhenMessageIsNull() { MIMEMessageConverter sut = new MIMEMessageConverter(); - sut.convertToMime(new MessageWithId.CreationMessageEntry(CreationMessageId.of("any"), null)); + sut.convertToMime(new ValueWithId.CreationMessageEntry(CreationMessageId.of("any"), null)); } @Test @@ -83,7 +84,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -107,7 +108,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -128,7 +129,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -148,7 +149,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -169,7 +170,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -190,7 +191,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -247,7 +248,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -267,7 +268,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -288,7 +289,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then @@ -310,7 +311,7 @@ public class MIMEMessageConverterTest { .build(); // When - Message result = sut.convertToMime(new MessageWithId.CreationMessageEntry( + Message result = sut.convertToMime(new ValueWithId.CreationMessageEntry( CreationMessageId.of("user|mailbox|1"), testMessage)); // Then --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
