You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by bt...@apache.org on 2018/03/13 08:12:54 UTC
[13/13] james-project git commit: JAMES-2346 sendMDN should send a
notification back to the original sender
JAMES-2346 sendMDN should send a notification back to the original sender
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo
Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/9481435b
Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/9481435b
Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/9481435b
Branch: refs/heads/master
Commit: 9481435b89ec32e3220202e4e2198e1bbb8b3b73
Parents: 1be875b
Author: benwa <bt...@linagora.com>
Authored: Thu Mar 8 16:20:15 2018 +0700
Committer: benwa <bt...@linagora.com>
Committed: Tue Mar 13 15:11:54 2018 +0700
----------------------------------------------------------------------
.../java/org/apache/james/mdn/MDNReport.java | 5 +
.../methods/integration/SendMDNMethodTest.java | 259 +++++++++++++++++--
.../jmap/methods/MIMEMessageConverter.java | 5 +-
.../james/jmap/methods/MessageAppender.java | 27 ++
.../james/jmap/methods/SendMDNProcessor.java | 179 ++++++++++++-
.../apache/james/jmap/methods/ValueWithId.java | 6 +-
.../org/apache/james/jmap/model/Envelope.java | 31 +++
.../org/apache/james/jmap/model/JmapMDN.java | 148 +++++++++++
.../java/org/apache/james/jmap/model/MDN.java | 148 -----------
.../james/jmap/model/SetMessagesRequest.java | 21 +-
.../james/jmap/model/SetMessagesResponse.java | 46 +++-
.../apache/james/jmap/model/JmapMDNTest.java | 125 +++++++++
.../org/apache/james/jmap/model/MDNTest.java | 125 ---------
.../jmap/model/SetMessagesResponseTest.java | 6 +-
14 files changed, 794 insertions(+), 337 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/mdn/src/main/java/org/apache/james/mdn/MDNReport.java
----------------------------------------------------------------------
diff --git a/mdn/src/main/java/org/apache/james/mdn/MDNReport.java b/mdn/src/main/java/org/apache/james/mdn/MDNReport.java
index 2878bfa..8785737 100644
--- a/mdn/src/main/java/org/apache/james/mdn/MDNReport.java
+++ b/mdn/src/main/java/org/apache/james/mdn/MDNReport.java
@@ -57,6 +57,11 @@ public class MDNReport {
private ImmutableList.Builder<Error> errorField = ImmutableList.builder();
private ImmutableList.Builder<ExtensionField> extensionFields = ImmutableList.builder();
+ public Builder reportingUserAgentField(String userAgentName) {
+ this.reportingUserAgentField = Optional.of(new ReportingUserAgent(userAgentName, Optional.empty()));
+ return this;
+ }
+
public Builder reportingUserAgentField(String userAgentName, String userAgentProduct) {
this.reportingUserAgentField = Optional.of(new ReportingUserAgent(userAgentName, Optional.ofNullable(userAgentProduct)));
return this;
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SendMDNMethodTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SendMDNMethodTest.java b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SendMDNMethodTest.java
index 059a7bb..d7690c7 100644
--- a/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SendMDNMethodTest.java
+++ b/server/protocols/jmap-integration-testing/jmap-integration-testing-common/src/test/java/org/apache/james/jmap/methods/integration/SendMDNMethodTest.java
@@ -20,26 +20,29 @@
package org.apache.james.jmap.methods.integration;
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.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;
import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.startsWith;
import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
import org.apache.http.client.utils.URIBuilder;
import org.apache.james.GuiceJamesServer;
import org.apache.james.jmap.HttpJmapAuthentication;
import org.apache.james.jmap.api.access.AccessToken;
import org.apache.james.mailbox.DefaultMailboxes;
-import org.apache.james.mailbox.model.MailboxConstants;
+import org.apache.james.mailbox.Role;
import org.apache.james.mailbox.model.MessageId;
import org.apache.james.mailbox.store.probe.MailboxProbe;
-import org.apache.james.mdn.action.mode.DispositionActionMode;
-import org.apache.james.mdn.sending.mode.DispositionSendingMode;
-import org.apache.james.mdn.type.DispositionType;
import org.apache.james.modules.MailboxProbeImpl;
import org.apache.james.probe.DataProbe;
import org.apache.james.utils.DataProbeImpl;
@@ -62,7 +65,6 @@ public abstract class SendMDNMethodTest {
private static final String PASSWORD = "password";
private static final String BOB_PASSWORD = "bobPassword";
-
protected abstract GuiceJamesServer createJmapServer();
protected abstract MessageId randomMessageId();
@@ -70,6 +72,7 @@ public abstract class SendMDNMethodTest {
protected abstract void await();
private AccessToken accessToken;
+ private AccessToken bobAccessToken;
private GuiceJamesServer jmapServer;
@Before
@@ -85,7 +88,6 @@ public abstract class SendMDNMethodTest {
.setConfig(newConfig().encoderConfig(encoderConfig().defaultContentCharset(StandardCharsets.UTF_8)))
.setPort(jmapServer.getProbe(JmapGuiceProbe.class).getJmapPort())
.build();
- RestAssured.enableLoggingOfRequestAndResponseIfValidationFails();
RestAssured.defaultParser = Parser.JSON;
dataProbe.addDomain(USERS_DOMAIN);
@@ -93,11 +95,39 @@ public abstract class SendMDNMethodTest {
dataProbe.addUser(BOB, BOB_PASSWORD);
mailboxProbe.createMailbox("#private", USERNAME, DefaultMailboxes.INBOX);
accessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(), USERNAME, PASSWORD);
+ bobAccessToken = HttpJmapAuthentication.authenticateJamesUser(baseUri(), BOB, BOB_PASSWORD);
+ await();
+ }
+
+ public void sendAnInitialMessage() {
+ String messageCreationId = "creationId";
+ String outboxId = getOutboxId(bobAccessToken);
+ String requestBody = "[" +
+ " [" +
+ " \"setMessages\"," +
+ " {" +
+ " \"create\": { \"" + messageCreationId + "\" : {" +
+ " \"from\": { \"name\": \"Bob\", \"email\": \"" + BOB + "\"}," +
+ " \"to\": [{ \"name\": \"User\", \"email\": \"" + USERNAME + "\"}]," +
+ " \"subject\": \"Message with an attachment\"," +
+ " \"textBody\": \"Test body, plain text version\"," +
+ " \"htmlBody\": \"Test <b>body</b>, HTML version\"," +
+ " \"mailboxIds\": [\"" + outboxId + "\"] " +
+ " }}" +
+ " }," +
+ " \"#0\"" +
+ " ]" +
+ "]";
+
+ with()
+ .header("Authorization", bobAccessToken.serialize())
+ .body(requestBody)
+ .post("/jmap")
+ .then()
+ .extract()
+ .body()
+ .path(ARGUMENTS + ".created." + messageCreationId + ".id");
- mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, DefaultMailboxes.OUTBOX);
- mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, DefaultMailboxes.TRASH);
- mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, DefaultMailboxes.DRAFTS);
- mailboxProbe.createMailbox(MailboxConstants.USER_NAMESPACE, USERNAME, DefaultMailboxes.SENT);
await();
}
@@ -116,20 +146,54 @@ public abstract class SendMDNMethodTest {
}
@Test
- public void sendMDNIsNotSupportedYet() {
+ public void sendMDNShouldReturnCreatedMessageId() {
+ sendAnInitialMessage();
+
+ List<String> messageIds = getMessageIdListForAccount(accessToken.serialize());
+
String creationId = "creation-1";
given()
.header("Authorization", accessToken.serialize())
.body("[[\"setMessages\", {\"sendMDN\": {" +
"\"" + creationId + "\":{" +
- " \"messageId\":\"" + randomMessageId().serialize() + "\"," +
+ " \"messageId\":\"" + messageIds.get(0) + "\"," +
+ " \"subject\":\"subject\"," +
+ " \"textBody\":\"textBody\"," +
+ " \"reportingUA\":\"reportingUA\"," +
+ " \"disposition\":{" +
+ " \"actionMode\":\"automatic-action\","+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
+ " }" +
+ "}" +
+ "}}, \"#0\"]]")
+ .when()
+ .post("/jmap")
+ .then()
+ .log().ifValidationFails()
+ .statusCode(200)
+ .body(NAME, equalTo("messagesSet"))
+ .body(ARGUMENTS + ".MDNSent." + creationId, notNullValue());
+ }
+
+ @Test
+ public void sendMDNShouldFailOnUnknownMessageId() {
+ sendAnInitialMessage();
+
+ String creationId = "creation-1";
+ String randomMessageId = randomMessageId().serialize();
+ given()
+ .header("Authorization", accessToken.serialize())
+ .body("[[\"setMessages\", {\"sendMDN\": {" +
+ "\"" + creationId + "\":{" +
+ " \"messageId\":\"" + randomMessageId + "\"," +
" \"subject\":\"subject\"," +
" \"textBody\":\"textBody\"," +
" \"reportingUA\":\"reportingUA\"," +
" \"disposition\":{" +
- " \"actionMode\":\"" + DispositionActionMode.Automatic.getValue() + "\","+
- " \"sendingMode\":\"" + DispositionSendingMode.Automatic.getValue() + "\","+
- " \"type\":\"" + DispositionType.Processed.getValue() + "\""+
+ " \"actionMode\":\"automatic-action\","+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
" }" +
"}" +
"}}, \"#0\"]]")
@@ -141,7 +205,107 @@ public abstract class SendMDNMethodTest {
.body(NAME, equalTo("messagesSet"))
.body(ARGUMENTS + ".MDNNotSent", hasEntry(
equalTo(creationId),
- hasEntry("type", "Not implemented yet")));
+ hasEntry("type", "invalidArgument")))
+ .body(ARGUMENTS + ".MDNNotSent", hasEntry(
+ equalTo(creationId),
+ hasEntry("description", "Message with id " + randomMessageId + " not found. Thus could not send MDN.")));
+ }
+
+ @Test
+ public void sendMDNShouldSendAMDNBackToTheOriginalMessageAuthor() {
+ sendAnInitialMessage();
+
+ List<String> messageIds = getMessageIdListForAccount(accessToken.serialize());
+
+ // USER sends a MDN back to BOB
+ String creationId = "creation-1";
+ with()
+ .header("Authorization", accessToken.serialize())
+ .body("[[\"setMessages\", {\"sendMDN\": {" +
+ "\"" + creationId + "\":{" +
+ " \"messageId\":\"" + messageIds.get(0) + "\"," +
+ " \"subject\":\"subject\"," +
+ " \"textBody\":\"Read confirmation\"," +
+ " \"reportingUA\":\"reportingUA\"," +
+ " \"disposition\":{" +
+ " \"actionMode\":\"automatic-action\","+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
+ " }" +
+ "}" +
+ "}}, \"#0\"]]")
+ .post("/jmap");
+
+ await();
+
+ // BOB should have received it
+ List<String> bobInboxMessageIds = listMessagesInMailbox(bobAccessToken, getInboxId(bobAccessToken));
+
+ given()
+ .header("Authorization", bobAccessToken.serialize())
+ .body("[[\"getMessages\", {\"ids\": [\"" + bobInboxMessageIds.get(0) + "\"]}, \"#0\"]]")
+ .when()
+ .post("/jmap")
+ .then()
+ .statusCode(200)
+ .body(ARGUMENTS + ".list[0].from.email", is(USERNAME))
+ .body(ARGUMENTS + ".list[0].to.email", contains(BOB))
+ .body(ARGUMENTS + ".list[0].hasAttachment", is(true))
+ .body(ARGUMENTS + ".list[0].textBody", is("Read confirmation"))
+ .body(ARGUMENTS + ".list[0].subject", is("subject"))
+ .body(ARGUMENTS + ".list[0].headers.Content-Type", startsWith("multipart/report;"))
+ .body(ARGUMENTS + ".list[0].attachments[0].type", startsWith("message/disposition-notification"));
+ }
+
+ @Test
+ public void sendMDNShouldPositionTheReportAsAnAttachment() {
+ sendAnInitialMessage();
+
+ List<String> messageIds = getMessageIdListForAccount(accessToken.serialize());
+
+ // USER sends a MDN back to BOB
+ String creationId = "creation-1";
+ with()
+ .header("Authorization", accessToken.serialize())
+ .body("[[\"setMessages\", {\"sendMDN\": {" +
+ "\"" + creationId + "\":{" +
+ " \"messageId\":\"" + messageIds.get(0) + "\"," +
+ " \"subject\":\"subject\"," +
+ " \"textBody\":\"Read confirmation\"," +
+ " \"reportingUA\":\"reportingUA\"," +
+ " \"disposition\":{" +
+ " \"actionMode\":\"automatic-action\","+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
+ " }" +
+ "}" +
+ "}}, \"#0\"]]")
+ .post("/jmap");
+
+ await();
+
+ // BOB should have received it
+ List<String> bobInboxMessageIds = listMessagesInMailbox(bobAccessToken, getInboxId(bobAccessToken));
+
+ String blobId = with()
+ .header("Authorization", bobAccessToken.serialize())
+ .body("[[\"getMessages\", {\"ids\": [\"" + bobInboxMessageIds.get(0) + "\"]}, \"#0\"]]")
+ .post("/jmap")
+ .then()
+ .extract()
+ .body()
+ .path(ARGUMENTS + ".list[0].attachments[0].blobId");
+
+ given()
+ .header("Authorization", bobAccessToken.serialize())
+ .when()
+ .get("/download/" + blobId)
+ .then()
+ .statusCode(200)
+ .body(containsString("Reporting-UA: reportingUA;"))
+ .body(containsString("Final-Recipient: rfc822; username@domain.tld"))
+ .body(containsString("Original-Message-ID: "))
+ .body(containsString("Disposition: automatic-action/MDN-sent-automatically;processed"));
}
@Test
@@ -156,9 +320,9 @@ public abstract class SendMDNMethodTest {
" \"textBody\":\"textBody\"," +
" \"reportingUA\":\"reportingUA\"," +
" \"disposition\":{" +
- " \"actionMode\":\"" + DispositionActionMode.Automatic.getValue() + "\","+
- " \"sendingMode\":\"" + DispositionSendingMode.Automatic.getValue() + "\","+
- " \"type\":\"" + DispositionType.Processed.getValue() + "\""+
+ " \"actionMode\":\"automatic-action\","+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
" }" +
"}" +
"}}, \"#0\"]]")
@@ -185,8 +349,8 @@ public abstract class SendMDNMethodTest {
" \"textBody\":\"textBody\"," +
" \"reportingUA\":\"reportingUA\"," +
" \"disposition\":{" +
- " \"sendingMode\":\"" + DispositionSendingMode.Automatic.getValue() + "\","+
- " \"type\":\"" + DispositionType.Processed.getValue() + "\""+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
" }" +
"}" +
"}}, \"#0\"]]")
@@ -203,7 +367,6 @@ public abstract class SendMDNMethodTest {
@Test
public void invalidEnumValuesInMDNShouldBeReported() {
String creationId = "creation-1";
- // Missing actionMode
given()
.header("Authorization", accessToken.serialize())
.body("[[\"setMessages\", {\"sendMDN\": {" +
@@ -214,8 +377,8 @@ public abstract class SendMDNMethodTest {
" \"reportingUA\":\"reportingUA\"," +
" \"disposition\":{" +
" \"actionMode\":\"invalid\","+
- " \"sendingMode\":\"" + DispositionSendingMode.Automatic.getValue() + "\","+
- " \"type\":\"" + DispositionType.Processed.getValue() + "\""+
+ " \"sendingMode\":\"MDN-sent-automatically\","+
+ " \"type\":\"processed\""+
" }" +
"}" +
"}}, \"#0\"]]")
@@ -229,4 +392,52 @@ public abstract class SendMDNMethodTest {
.body(ARGUMENTS + ".description", containsString("Unrecognized MDN Disposition action mode invalid. Should be one of [manual-action, automatic-action]"));
}
+ private String getInboxId(AccessToken accessToken) {
+ return getMailboxId(accessToken, Role.INBOX);
+ }
+
+ private String getOutboxId(AccessToken accessToken) {
+ return getMailboxId(accessToken, Role.OUTBOX);
+ }
+
+ private String getMailboxId(AccessToken accessToken, Role role) {
+ return getAllMailboxesIds(accessToken).stream()
+ .filter(x -> x.get("role").equalsIgnoreCase(role.serialize()))
+ .map(x -> x.get("id"))
+ .findFirst().get();
+ }
+
+ private List<Map<String, String>> getAllMailboxesIds(AccessToken accessToken) {
+ return with()
+ .header("Authorization", accessToken.serialize())
+ .body("[[\"getMailboxes\", {\"properties\": [\"role\", \"id\"]}, \"#0\"]]")
+ .post("/jmap")
+ .andReturn()
+ .body()
+ .jsonPath()
+ .getList(ARGUMENTS + ".list");
+ }
+
+ public List<String> getMessageIdListForAccount(String accessToken) {
+ return with()
+ .header("Authorization", accessToken)
+ .body("[[\"getMessageList\", {}, \"#0\"]]")
+ .post("/jmap")
+ .then()
+ .extract()
+ .body()
+ .path(ARGUMENTS + ".messageIds");
+ }
+
+ public List<String> listMessagesInMailbox(AccessToken accessToken, String mailboxId) {
+ return with()
+ .header("Authorization", accessToken.serialize())
+ .body("[[\"getMessageList\", {\"filter\":{\"inMailboxes\":[\"" + mailboxId + "\"]}}, \"#0\"]]")
+ .post("/jmap")
+ .then()
+ .extract()
+ .body()
+ .path(ARGUMENTS + ".messageIds");
+ }
+
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/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 49688b1..a8b1cbf 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
@@ -107,11 +107,14 @@ public class MIMEMessageConverter {
}
public byte[] convert(ValueWithId.CreationMessageEntry creationMessageEntry, ImmutableList<MessageAttachment> messageAttachments) {
+ return asBytes(convertToMime(creationMessageEntry, messageAttachments));
+ }
+ public byte[] asBytes(Message message) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
DefaultMessageWriter writer = new DefaultMessageWriter();
try {
- writer.writeMessage(convertToMime(creationMessageEntry, messageAttachments), buffer);
+ writer.writeMessage(message, buffer);
} catch (IOException e) {
throw Throwables.propagate(e);
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageAppender.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageAppender.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageAppender.java
index c74f59d..409bd91 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageAppender.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageAppender.java
@@ -30,6 +30,7 @@ import javax.mail.util.SharedByteArrayInputStream;
import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry;
import org.apache.james.jmap.model.Attachment;
import org.apache.james.jmap.model.CreationMessage;
+import org.apache.james.jmap.model.Keywords;
import org.apache.james.jmap.model.MessageFactory;
import org.apache.james.mailbox.AttachmentManager;
import org.apache.james.mailbox.MailboxManager;
@@ -97,6 +98,32 @@ public class MessageAppender {
.build();
}
+ public MessageFactory.MetaDataWithContent appendMessageInMailbox(org.apache.james.mime4j.dom.Message message,
+ MessageManager messageManager,
+ List<MessageAttachment> attachments,
+ Flags flags,
+ MailboxSession session) throws MailboxException {
+
+
+ byte[] messageContent = mimeMessageConverter.asBytes(message);
+ SharedByteArrayInputStream content = new SharedByteArrayInputStream(messageContent);
+ Date internalDate = new Date();
+ boolean notRecent = false;
+
+ ComposedMessageId appendedMessage = messageManager.appendMessage(content, internalDate, session, notRecent, flags);
+
+ return MessageFactory.MetaDataWithContent.builder()
+ .uid(appendedMessage.getUid())
+ .keywords(Keywords.factory().fromFlags(flags))
+ .internalDate(internalDate.toInstant())
+ .sharedContent(content)
+ .size(messageContent.length)
+ .attachments(attachments)
+ .mailboxId(messageManager.getId())
+ .messageId(appendedMessage.getMessageId())
+ .build();
+ }
+
public MessageFactory.MetaDataWithContent appendMessageInMailbox(CreationMessageEntry createdEntry,
MailboxId targetMailbox,
MailboxSession session) throws MailboxException {
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SendMDNProcessor.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SendMDNProcessor.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SendMDNProcessor.java
index c0dfa27..5f13d44 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SendMDNProcessor.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/SendMDNProcessor.java
@@ -21,38 +21,195 @@ package org.apache.james.jmap.methods;
import static org.apache.james.jmap.methods.Method.JMAP_PREFIX;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
import javax.inject.Inject;
+import javax.mail.Flags;
+import javax.mail.MessagingException;
+import org.apache.james.core.User;
+import org.apache.james.jmap.exceptions.MessageNotFoundException;
+import org.apache.james.jmap.model.Envelope;
+import org.apache.james.jmap.model.JmapMDN;
+import org.apache.james.jmap.model.MDNDisposition;
+import org.apache.james.jmap.model.MessageFactory;
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.utils.SystemMailboxesProvider;
import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.MessageIdManager;
+import org.apache.james.mailbox.MessageManager;
+import org.apache.james.mailbox.Role;
+import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.model.Attachment;
+import org.apache.james.mailbox.model.FetchGroupImpl;
+import org.apache.james.mailbox.model.MessageAttachment;
+import org.apache.james.mailbox.model.MessageId;
+import org.apache.james.mailbox.model.MessageResult;
+import org.apache.james.mdn.MDN;
+import org.apache.james.mdn.MDNReport;
+import org.apache.james.mdn.fields.Disposition;
import org.apache.james.metrics.api.MetricFactory;
+import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.dom.field.ParseException;
+import org.apache.james.mime4j.message.DefaultMessageBuilder;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.apache.james.mime4j.util.MimeUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.ImmutableList;
public class SendMDNProcessor implements SetMessagesProcessor {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(SendMDNProcessor.class);
+ private static final MimeConfig MIME_ENTITY_CONFIG = MimeConfig.custom()
+ .setMaxContentLen(-1)
+ .setMaxHeaderCount(-1)
+ .setMaxHeaderLen(-1)
+ .setMaxLineLen(-1)
+ .build();
+
private final MetricFactory metricFactory;
+ private final SystemMailboxesProvider systemMailboxesProvider;
+ private final MessageIdManager messageIdManager;
+ private final MessageAppender messageAppender;
+ private final MessageSender messageSender;
@Inject
- public SendMDNProcessor(MetricFactory metricFactory) {
+ public SendMDNProcessor(MetricFactory metricFactory, SystemMailboxesProvider systemMailboxesProvider,
+ MessageIdManager messageIdManager, MessageAppender messageAppender, MessageSender messageSender) {
this.metricFactory = metricFactory;
+ this.systemMailboxesProvider = systemMailboxesProvider;
+ this.messageIdManager = messageIdManager;
+ this.messageAppender = messageAppender;
+ this.messageSender = messageSender;
}
@Override
public SetMessagesResponse process(SetMessagesRequest request, MailboxSession mailboxSession) {
return metricFactory.withMetric(JMAP_PREFIX + "SendMDN",
- () -> handleMDNCreation(request));
+ () -> handleMDNCreation(request, mailboxSession));
+ }
+
+ private SetMessagesResponse handleMDNCreation(SetMessagesRequest request, MailboxSession mailboxSession) {
+ return request.getSendMDN()
+ .stream()
+ .map(MDNCreationEntry -> handleMDNCreation(MDNCreationEntry, mailboxSession))
+ .reduce(SetMessagesResponse.builder(), SetMessagesResponse.Builder::mergeWith)
+ .build();
+ }
+
+ private SetMessagesResponse.Builder handleMDNCreation(ValueWithId.MDNCreationEntry MDNCreationEntry, MailboxSession mailboxSession) {
+ try {
+ MessageId messageId = sendMdn(MDNCreationEntry, mailboxSession);
+ return SetMessagesResponse.builder()
+ .mdnSent(MDNCreationEntry.getCreationId(), messageId);
+ } catch (MessageNotFoundException e) {
+ return SetMessagesResponse.builder()
+ .mdnNotSent(MDNCreationEntry.getCreationId(),
+ SetError.builder()
+ .description(String.format("Message with id %s not found. Thus could not send MDN.",
+ MDNCreationEntry.getValue().getMessageId().serialize()))
+ .type("invalidArgument")
+ .build());
+
+ } catch (Exception e) {
+ LOGGER.error("Error while sending MDN", e);
+ return SetMessagesResponse.builder()
+ .mdnNotSent(MDNCreationEntry.getCreationId(),
+ SetError.builder()
+ .description(String.format("Could not send MDN %s", MDNCreationEntry.getCreationId().getId()))
+ .type("error")
+ .build());
+ }
}
- public SetMessagesResponse handleMDNCreation(SetMessagesRequest request) {
- SetMessagesResponse.Builder builder = SetMessagesResponse.builder();
+ private MessageId sendMdn(ValueWithId.MDNCreationEntry MDNCreationEntry, MailboxSession mailboxSession) throws MailboxException, IOException, MessagingException, ParseException, MessageNotFoundException {
+ JmapMDN mdn = MDNCreationEntry.getValue();
+ Message originalMessage = retrieveOriginalMessage(mdn, mailboxSession);
+ MDNReport mdnReport = generateReport(mdn, originalMessage, mailboxSession);
+ List<MessageAttachment> reportAsAttachment = ImmutableList.of(convertReportToAttachment(mdnReport));
+ User user = User.fromUsername(mailboxSession.getUser().getUserName());
+
+ Message mdnAnswer = generateMDNMessage(originalMessage, mdn, mdnReport, user);
+
+ Flags seen = new Flags(Flags.Flag.SEEN);
+ MessageFactory.MetaDataWithContent metaDataWithContent = messageAppender.appendMessageInMailbox(mdnAnswer,
+ getOutbox(mailboxSession), reportAsAttachment, seen, mailboxSession);
- request.getSendMDN()
- .forEach(creationMDNEntry -> builder.MDNNotSent(creationMDNEntry.getCreationId(),
- SetError.builder()
- .description(String.format("Could not send MDN %s", creationMDNEntry.getCreationId().getId()))
- .type("Not implemented yet")
- .build()));
+ messageSender.sendMessage(metaDataWithContent,
+ Envelope.fromMime4JMessage(mdnAnswer), mailboxSession);
- return builder.build();
+ return metaDataWithContent.getMessageId();
}
+
+ private Message generateMDNMessage(Message originalMessage, JmapMDN mdn, MDNReport mdnReport, User user) throws ParseException, IOException {
+ return MDN.builder()
+ .report(mdnReport)
+ .humanReadableText(mdn.getTextBody())
+ .build()
+ .asMime4JMessageBuilder()
+ .setTo(originalMessage.getSender().getAddress())
+ .setFrom(user.asString())
+ .setSubject(mdn.getSubject())
+ .setMessageId(MimeUtil.createUniqueMessageId(user.getDomainPart().orElse(null)))
+ .build();
+ }
+
+ private Message retrieveOriginalMessage(JmapMDN mdn, MailboxSession mailboxSession) throws MailboxException, IOException, MessageNotFoundException {
+ List<MessageResult> messages = messageIdManager.getMessages(ImmutableList.of(mdn.getMessageId()),
+ FetchGroupImpl.HEADERS,
+ mailboxSession);
+
+ if (messages.size() == 0) {
+ throw new MessageNotFoundException();
+ }
+
+ DefaultMessageBuilder messageBuilder = new DefaultMessageBuilder();
+ messageBuilder.setMimeEntityConfig(MIME_ENTITY_CONFIG);
+ messageBuilder.setDecodeMonitor(DecodeMonitor.SILENT);
+ return messageBuilder.parseMessage(messages.get(0).getHeaders().getInputStream());
+ }
+
+ private MessageAttachment convertReportToAttachment(MDNReport mdnReport) {
+ Attachment attachment = Attachment.builder()
+ .bytes(mdnReport.formattedValue().getBytes(StandardCharsets.UTF_8))
+ .type(MDN.DISPOSITION_CONTENT_TYPE)
+ .build();
+
+ return MessageAttachment.builder()
+ .attachment(attachment)
+ .isInline(true)
+ .build();
+ }
+
+ private MDNReport generateReport(JmapMDN mdn, Message originalMessage, MailboxSession mailboxSession) {
+ return MDNReport.builder()
+ .dispositionField(generateDisposition(mdn.getDisposition()))
+ .originalRecipientField(mailboxSession.getUser().getUserName())
+ .originalMessageIdField(originalMessage.getMessageId())
+ .finalRecipientField(mailboxSession.getUser().getUserName())
+ .reportingUserAgentField(mdn.getReportingUA())
+ .build();
+ }
+
+ private Disposition generateDisposition(MDNDisposition disposition) {
+ return Disposition.builder()
+ .actionMode(disposition.getActionMode())
+ .sendingMode(disposition.getSendingMode())
+ .type(disposition.getType())
+ .build();
+ }
+
+ private MessageManager getOutbox(MailboxSession mailboxSession) throws MailboxException {
+ return systemMailboxesProvider.getMailboxByRole(Role.OUTBOX, mailboxSession)
+ .findAny()
+ .orElseThrow(() -> new IllegalStateException("User don't have an Outbox"));
+ }
+
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/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
index e8ce60c..6304951 100644
--- 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
@@ -21,7 +21,7 @@ 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.MDN;
+import org.apache.james.jmap.model.JmapMDN;
import org.apache.james.jmap.model.Message;
import org.apache.james.jmap.model.SetError;
@@ -51,8 +51,8 @@ public class ValueWithId<T> {
}
}
- public static class CreationMDNEntry extends ValueWithId<MDN> {
- public CreationMDNEntry(CreationMessageId creationId, MDN mdn) {
+ public static class MDNCreationEntry extends ValueWithId<JmapMDN> {
+ public MDNCreationEntry(CreationMessageId creationId, JmapMDN mdn) {
super(creationId, mdn);
}
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Envelope.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Envelope.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Envelope.java
index c4d80f9..28f61c1 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Envelope.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/Envelope.java
@@ -21,12 +21,17 @@ package org.apache.james.jmap.model;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.apache.james.core.MailAddress;
+import org.apache.james.mime4j.dom.address.AddressList;
+import org.apache.james.mime4j.dom.address.Mailbox;
+import org.apache.james.mime4j.dom.address.MailboxList;
import org.apache.james.util.StreamUtils;
+import com.github.fge.lambdas.Throwing;
import com.github.steveash.guavate.Guavate;
import com.google.common.base.Preconditions;
@@ -46,11 +51,37 @@ public class Envelope {
.collect(Guavate.toImmutableSet()));
}
+ public static Envelope fromMime4JMessage(org.apache.james.mime4j.dom.Message mime4JMessage) {
+ MailAddress sender = mime4JMessage.getFrom()
+ .stream()
+ .findAny()
+ .map(Mailbox::getAddress)
+ .map(Throwing.function(MailAddress::new))
+ .orElseThrow(() -> new RuntimeException("Sender is mandatory"));
+
+ Stream<MailAddress> to = emailersToMailAddresses(mime4JMessage.getTo());
+ Stream<MailAddress> cc = emailersToMailAddresses(mime4JMessage.getCc());
+ Stream<MailAddress> bcc = emailersToMailAddresses(mime4JMessage.getBcc());
+
+ return new Envelope(sender,
+ StreamUtils.flatten(Stream.of(to, cc, bcc))
+ .collect(Guavate.toImmutableSet()));
+ }
+
private static Stream<MailAddress> emailersToMailAddresses(List<Emailer> emailers) {
return emailers.stream()
.map(Emailer::toMailAddress);
}
+ private static Stream<MailAddress> emailersToMailAddresses(AddressList addresses) {
+ return Optional.ofNullable(addresses)
+ .map(AddressList::flatten)
+ .map(MailboxList::stream)
+ .orElse(Stream.of())
+ .map(Mailbox::getAddress)
+ .map(Throwing.function(MailAddress::new));
+ }
+
private final MailAddress from;
private final Set<MailAddress> recipients;
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/JmapMDN.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/JmapMDN.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/JmapMDN.java
new file mode 100644
index 0000000..0e1df56
--- /dev/null
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/JmapMDN.java
@@ -0,0 +1,148 @@
+/****************************************************************
+ * 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.model;
+
+import java.util.Objects;
+
+import org.apache.james.mailbox.model.MessageId;
+
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Preconditions;
+
+@JsonDeserialize(builder = JmapMDN.Builder.class)
+public class JmapMDN {
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ @JsonPOJOBuilder(withPrefix = "")
+ public static class Builder {
+ private MessageId messageId;
+ private String subject;
+ private String textBody;
+ private String reportingUA;
+ private MDNDisposition disposition;
+
+ public Builder messageId(MessageId messageId) {
+ this.messageId = messageId;
+ return this;
+ }
+
+ public Builder subject(String subject) {
+ this.subject = subject;
+ return this;
+ }
+
+ public Builder textBody(String textBody) {
+ this.textBody = textBody;
+ return this;
+ }
+
+ public Builder reportingUA(String reportingUA) {
+ this.reportingUA = reportingUA;
+ return this;
+ }
+
+ public Builder disposition(MDNDisposition disposition) {
+ this.disposition = disposition;
+ return this;
+ }
+
+ public JmapMDN build() {
+ Preconditions.checkState(messageId != null, "'messageId' is mandatory");
+ Preconditions.checkState(subject != null, "'subject' is mandatory");
+ Preconditions.checkState(textBody != null, "'textBody' is mandatory");
+ Preconditions.checkState(reportingUA != null, "'reportingUA' is mandatory");
+ Preconditions.checkState(disposition != null, "'disposition' is mandatory");
+
+ return new JmapMDN(messageId, subject, textBody, reportingUA, disposition);
+ }
+
+ }
+
+ private final MessageId messageId;
+ private final String subject;
+ private final String textBody;
+ private final String reportingUA;
+ private final MDNDisposition disposition;
+
+ @VisibleForTesting
+ JmapMDN(MessageId messageId, String subject, String textBody, String reportingUA, MDNDisposition disposition) {
+ this.messageId = messageId;
+ this.subject = subject;
+ this.textBody = textBody;
+ this.reportingUA = reportingUA;
+ this.disposition = disposition;
+ }
+
+ public MessageId getMessageId() {
+ return messageId;
+ }
+
+ public String getSubject() {
+ return subject;
+ }
+
+ public String getTextBody() {
+ return textBody;
+ }
+
+ public String getReportingUA() {
+ return reportingUA;
+ }
+
+ public MDNDisposition getDisposition() {
+ return disposition;
+ }
+
+ @Override
+ public final boolean equals(Object o) {
+ if (o instanceof JmapMDN) {
+ JmapMDN that = (JmapMDN) o;
+
+ return Objects.equals(this.messageId, that.messageId)
+ && Objects.equals(this.subject, that.subject)
+ && Objects.equals(this.textBody, that.textBody)
+ && Objects.equals(this.reportingUA, that.reportingUA)
+ && Objects.equals(this.disposition, that.disposition);
+ }
+ return false;
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(messageId, subject, textBody, reportingUA, disposition);
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("messageId", messageId)
+ .add("subject", subject)
+ .add("textBody", textBody)
+ .add("reportingUA", reportingUA)
+ .add("mdnDisposition", disposition)
+ .toString();
+ }
+}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MDN.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MDN.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MDN.java
deleted file mode 100644
index cf54031..0000000
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/model/MDN.java
+++ /dev/null
@@ -1,148 +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.model;
-
-import java.util.Objects;
-
-import org.apache.james.mailbox.model.MessageId;
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Preconditions;
-
-@JsonDeserialize(builder = MDN.Builder.class)
-public class MDN {
-
- public static Builder builder() {
- return new Builder();
- }
-
- @JsonPOJOBuilder(withPrefix = "")
- public static class Builder {
- private MessageId messageId;
- private String subject;
- private String textBody;
- private String reportingUA;
- private MDNDisposition disposition;
-
- public Builder messageId(MessageId messageId) {
- this.messageId = messageId;
- return this;
- }
-
- public Builder subject(String subject) {
- this.subject = subject;
- return this;
- }
-
- public Builder textBody(String textBody) {
- this.textBody = textBody;
- return this;
- }
-
- public Builder reportingUA(String reportingUA) {
- this.reportingUA = reportingUA;
- return this;
- }
-
- public Builder disposition(MDNDisposition disposition) {
- this.disposition = disposition;
- return this;
- }
-
- public MDN build() {
- Preconditions.checkState(messageId != null, "'messageId' is mandatory");
- Preconditions.checkState(subject != null, "'subject' is mandatory");
- Preconditions.checkState(textBody != null, "'textBody' is mandatory");
- Preconditions.checkState(reportingUA != null, "'reportingUA' is mandatory");
- Preconditions.checkState(disposition != null, "'disposition' is mandatory");
-
- return new MDN(messageId, subject, textBody, reportingUA, disposition);
- }
-
- }
-
- private final MessageId messageId;
- private final String subject;
- private final String textBody;
- private final String reportingUA;
- private final MDNDisposition disposition;
-
- @VisibleForTesting
- MDN(MessageId messageId, String subject, String textBody, String reportingUA, MDNDisposition disposition) {
- this.messageId = messageId;
- this.subject = subject;
- this.textBody = textBody;
- this.reportingUA = reportingUA;
- this.disposition = disposition;
- }
-
- public MessageId getMessageId() {
- return messageId;
- }
-
- public String getSubject() {
- return subject;
- }
-
- public String getTextBody() {
- return textBody;
- }
-
- public String getReportingUA() {
- return reportingUA;
- }
-
- public MDNDisposition getDisposition() {
- return disposition;
- }
-
- @Override
- public final boolean equals(Object o) {
- if (o instanceof MDN) {
- MDN that = (MDN) o;
-
- return Objects.equals(this.messageId, that.messageId)
- && Objects.equals(this.subject, that.subject)
- && Objects.equals(this.textBody, that.textBody)
- && Objects.equals(this.reportingUA, that.reportingUA)
- && Objects.equals(this.disposition, that.disposition);
- }
- return false;
- }
-
- @Override
- public final int hashCode() {
- return Objects.hash(messageId, subject, textBody, reportingUA, disposition);
- }
-
- @Override
- public String toString() {
- return MoreObjects.toStringHelper(this)
- .add("messageId", messageId)
- .add("subject", subject)
- .add("textBody", textBody)
- .add("reportingUA", reportingUA)
- .add("mdnDisposition", disposition)
- .toString();
- }
-}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/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 2f3cd68..ec6b1a8 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
@@ -28,8 +28,8 @@ 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.CreationMDNEntry;
import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry;
+import org.apache.james.jmap.methods.ValueWithId.MDNCreationEntry;
import org.apache.james.mailbox.model.MessageId;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
@@ -54,7 +54,7 @@ public class SetMessagesRequest implements JmapRequest {
private String accountId;
private String ifInState;
private HashMap<CreationMessageId, CreationMessage> create;
- private HashMap<CreationMessageId, MDN> sendMDN;
+ private HashMap<CreationMessageId, JmapMDN> sendMDN;
private ImmutableMap.Builder<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> updatesProvider;
private ImmutableList.Builder<MessageId> destroy;
@@ -90,12 +90,12 @@ public class SetMessagesRequest implements JmapRequest {
return this;
}
- public Builder sendMDN(CreationMessageId creationMessageId, MDN mdn) {
+ public Builder sendMDN(CreationMessageId creationMessageId, JmapMDN mdn) {
this.sendMDN.put(creationMessageId, mdn);
return this;
}
- public Builder sendMDN(Map<CreationMessageId, MDN> mdns) {
+ public Builder sendMDN(Map<CreationMessageId, JmapMDN> mdns) {
this.sendMDN.putAll(mdns);
return this;
}
@@ -121,9 +121,9 @@ public class SetMessagesRequest implements JmapRequest {
.collect(Guavate.toImmutableList());
}
- private ImmutableList<CreationMDNEntry> mdnSendings() {
+ private ImmutableList<MDNCreationEntry> mdnSendings() {
return sendMDN.entrySet().stream()
- .map(entry -> new CreationMDNEntry(entry.getKey(), entry.getValue()))
+ .map(entry -> new MDNCreationEntry(entry.getKey(), entry.getValue()))
.collect(Guavate.toImmutableList());
}
}
@@ -131,13 +131,14 @@ public class SetMessagesRequest implements JmapRequest {
private final Optional<String> accountId;
private final Optional<String> ifInState;
private final List<CreationMessageEntry> create;
- private final List<CreationMDNEntry> sendMDN;
+ private final List<MDNCreationEntry> sendMDN;
private final Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update;
private final List<MessageId> destroy;
@VisibleForTesting SetMessagesRequest(Optional<String> accountId, Optional<String> ifInState,
- List<CreationMessageEntry> create, List<CreationMDNEntry> sendMDN, Map<MessageId,
- Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update, List<MessageId> destroy) {
+ List<CreationMessageEntry> create, List<MDNCreationEntry> sendMDN,
+ Map<MessageId, Function<UpdateMessagePatchConverter, UpdateMessagePatch>> update,
+ List<MessageId> destroy) {
this.accountId = accountId;
this.ifInState = ifInState;
this.create = create;
@@ -158,7 +159,7 @@ public class SetMessagesRequest implements JmapRequest {
return create;
}
- public List<CreationMDNEntry> getSendMDN() {
+ public List<MDNCreationEntry> getSendMDN() {
return sendMDN;
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/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 5700df0..c8e20e6 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
@@ -51,19 +51,21 @@ public class SetMessagesResponse implements Method.Response {
private String oldState;
private String newState;
private final ImmutableMap.Builder<CreationMessageId, Message> created;
+ private final ImmutableMap.Builder<CreationMessageId, MessageId> mdnSent;
private final ImmutableList.Builder<MessageId> updated;
private final ImmutableList.Builder<MessageId> destroyed;
private final ImmutableMap.Builder<CreationMessageId, SetError> notCreated;
- private final ImmutableMap.Builder<CreationMessageId, SetError> MDNNotSent;
+ private final ImmutableMap.Builder<CreationMessageId, SetError> mdnNotSent;
private final ImmutableMap.Builder<MessageId, SetError> notUpdated;
private final ImmutableMap.Builder<MessageId, SetError> notDestroyed;
private Builder() {
created = ImmutableMap.builder();
+ mdnSent = ImmutableMap.builder();
updated = ImmutableList.builder();
destroyed = ImmutableList.builder();
notCreated = ImmutableMap.builder();
- MDNNotSent = ImmutableMap.builder();
+ mdnNotSent = ImmutableMap.builder();
notUpdated = ImmutableMap.builder();
notDestroyed = ImmutableMap.builder();
}
@@ -90,6 +92,16 @@ public class SetMessagesResponse implements Method.Response {
return this;
}
+ public Builder mdnSent(CreationMessageId creationMessageId, MessageId messageId) {
+ this.mdnSent.put(creationMessageId, messageId);
+ return this;
+ }
+
+ public Builder mdnSent(ImmutableMap<CreationMessageId, MessageId> sent) {
+ this.mdnSent.putAll(sent);
+ return this;
+ }
+
public Builder updated(List<MessageId> updated) {
this.updated.addAll(updated);
return this;
@@ -110,13 +122,13 @@ public class SetMessagesResponse implements Method.Response {
return this;
}
- public Builder MDNNotSent(Map<CreationMessageId, SetError> notCreated) {
- this.MDNNotSent.putAll(notCreated);
+ public Builder mdnNotSent(Map<CreationMessageId, SetError> notCreated) {
+ this.mdnNotSent.putAll(notCreated);
return this;
}
- public Builder MDNNotSent(CreationMessageId creationMessageId, SetError error) {
- this.MDNNotSent.put(creationMessageId, error);
+ public Builder mdnNotSent(CreationMessageId creationMessageId, SetError error) {
+ this.mdnNotSent.put(creationMessageId, error);
return this;
}
@@ -146,8 +158,8 @@ public class SetMessagesResponse implements Method.Response {
public SetMessagesResponse build() {
return new SetMessagesResponse(accountId, oldState, newState,
- created.build(), updated.build(), destroyed.build(),
- notCreated.build(), MDNNotSent.build(), notUpdated.build(), notDestroyed.build());
+ created.build(), mdnSent.build(), updated.build(), destroyed.build(),
+ notCreated.build(), mdnNotSent.build(), notUpdated.build(), notDestroyed.build());
}
}
@@ -155,23 +167,25 @@ public class SetMessagesResponse implements Method.Response {
private final String oldState;
private final String newState;
private final ImmutableMap<CreationMessageId, Message> created;
+ private final ImmutableMap<CreationMessageId, MessageId> mdnSent;
private final ImmutableList<MessageId> updated;
private final ImmutableList<MessageId> destroyed;
private final ImmutableMap<CreationMessageId, SetError> notCreated;
- private final ImmutableMap<CreationMessageId, SetError> MDNNotSent;
+ private final ImmutableMap<CreationMessageId, SetError> mdnNotSent;
private final ImmutableMap<MessageId, SetError> notUpdated;
private final ImmutableMap<MessageId, SetError> notDestroyed;
- @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, ImmutableMap<CreationMessageId, Message> created, ImmutableList<MessageId> updated, ImmutableList<MessageId> destroyed,
+ @VisibleForTesting SetMessagesResponse(String accountId, String oldState, String newState, ImmutableMap<CreationMessageId, Message> created, ImmutableMap<CreationMessageId, MessageId> mdnSent, ImmutableList<MessageId> updated, ImmutableList<MessageId> destroyed,
ImmutableMap<CreationMessageId, SetError> notCreated, ImmutableMap<CreationMessageId, SetError> mdnNotSent, ImmutableMap<MessageId, SetError> notUpdated, ImmutableMap<MessageId, SetError> notDestroyed) {
this.accountId = accountId;
this.oldState = oldState;
this.newState = newState;
this.created = created;
+ this.mdnSent = mdnSent;
this.updated = updated;
this.destroyed = destroyed;
this.notCreated = notCreated;
- this.MDNNotSent = mdnNotSent;
+ this.mdnNotSent = mdnNotSent;
this.notUpdated = notUpdated;
this.notDestroyed = notDestroyed;
}
@@ -212,9 +226,14 @@ public class SetMessagesResponse implements Method.Response {
return notDestroyed;
}
+ @JsonProperty("MDNSent")
+ public ImmutableMap<CreationMessageId, MessageId> getMDNSent() {
+ return mdnSent;
+ }
+
@JsonProperty("MDNNotSent")
public ImmutableMap<CreationMessageId, SetError> getMDNNotSent() {
- return MDNNotSent;
+ return mdnNotSent;
}
public SetMessagesResponse.Builder mergeInto(SetMessagesResponse.Builder responseBuilder) {
@@ -224,7 +243,8 @@ public class SetMessagesResponse implements Method.Response {
responseBuilder.notCreated(getNotCreated());
responseBuilder.notUpdated(getNotUpdated());
responseBuilder.notDestroyed(getNotDestroyed());
- responseBuilder.MDNNotSent(getMDNNotSent());
+ responseBuilder.mdnNotSent(getMDNNotSent());
+ responseBuilder.mdnSent(getMDNSent());
if (! Strings.isNullOrEmpty(getAccountId())) {
responseBuilder.accountId(getAccountId());
}
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/JmapMDNTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/JmapMDNTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/JmapMDNTest.java
new file mode 100644
index 0000000..5cb045d
--- /dev/null
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/JmapMDNTest.java
@@ -0,0 +1,125 @@
+/****************************************************************
+ * 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.model;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import org.apache.james.mailbox.model.TestMessageId;
+import org.apache.james.mdn.action.mode.DispositionActionMode;
+import org.apache.james.mdn.sending.mode.DispositionSendingMode;
+import org.apache.james.mdn.type.DispositionType;
+import org.junit.Test;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+
+public class JmapMDNTest {
+
+ public static final String TEXT_BODY = "text body";
+ public static final String SUBJECT = "subject";
+ public static final String REPORTING_UA = "reportingUA";
+ public static final MDNDisposition DISPOSITION = MDNDisposition.builder()
+ .actionMode(DispositionActionMode.Automatic)
+ .sendingMode(DispositionSendingMode.Automatic)
+ .type(DispositionType.Processed)
+ .build();
+ public static final TestMessageId MESSAGE_ID = TestMessageId.of(45);
+
+ @Test
+ public void shouldMatchBeanContract() {
+ EqualsVerifier.forClass(JmapMDN.class)
+ .allFieldsShouldBeUsed()
+ .verify();
+ }
+
+ @Test
+ public void builderShouldReturnObjectWhenAllFieldsAreValid() {
+ assertThat(
+ JmapMDN.builder()
+ .disposition(DISPOSITION)
+ .messageId(MESSAGE_ID)
+ .reportingUA(REPORTING_UA)
+ .subject(SUBJECT)
+ .textBody(TEXT_BODY)
+ .build())
+ .isEqualTo(new JmapMDN(MESSAGE_ID, SUBJECT, TEXT_BODY, REPORTING_UA, DISPOSITION));
+ }
+
+ @Test
+ public void dispositionIsCompulsory() {
+ assertThatThrownBy(() ->
+ JmapMDN.builder()
+ .messageId(MESSAGE_ID)
+ .reportingUA(REPORTING_UA)
+ .subject(SUBJECT)
+ .textBody(TEXT_BODY)
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void messageIdIsCompulsory() {
+ assertThatThrownBy(() ->
+ JmapMDN.builder()
+ .disposition(DISPOSITION)
+ .reportingUA(REPORTING_UA)
+ .subject(SUBJECT)
+ .textBody(TEXT_BODY)
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void reportingUAIsCompulsory() {
+ assertThatThrownBy(() ->
+ JmapMDN.builder()
+ .disposition(DISPOSITION)
+ .messageId(MESSAGE_ID)
+ .subject(SUBJECT)
+ .textBody(TEXT_BODY)
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void subjectIsCompulsory() {
+ assertThatThrownBy(() ->
+ JmapMDN.builder()
+ .disposition(DISPOSITION)
+ .messageId(MESSAGE_ID)
+ .reportingUA(REPORTING_UA)
+ .textBody(TEXT_BODY)
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+ @Test
+ public void textBodyIsCompulsory() {
+ assertThatThrownBy(() ->
+ JmapMDN.builder()
+ .disposition(DISPOSITION)
+ .messageId(MESSAGE_ID)
+ .reportingUA(REPORTING_UA)
+ .subject(SUBJECT)
+ .build())
+ .isInstanceOf(IllegalStateException.class);
+ }
+
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MDNTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MDNTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MDNTest.java
deleted file mode 100644
index 1527719..0000000
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/MDNTest.java
+++ /dev/null
@@ -1,125 +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.model;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-
-import org.apache.james.mailbox.model.TestMessageId;
-import org.apache.james.mdn.action.mode.DispositionActionMode;
-import org.apache.james.mdn.sending.mode.DispositionSendingMode;
-import org.apache.james.mdn.type.DispositionType;
-import org.junit.Test;
-
-import nl.jqno.equalsverifier.EqualsVerifier;
-
-public class MDNTest {
-
- public static final String TEXT_BODY = "text body";
- public static final String SUBJECT = "subject";
- public static final String REPORTING_UA = "reportingUA";
- public static final MDNDisposition DISPOSITION = MDNDisposition.builder()
- .actionMode(DispositionActionMode.Automatic)
- .sendingMode(DispositionSendingMode.Automatic)
- .type(DispositionType.Processed)
- .build();
- public static final TestMessageId MESSAGE_ID = TestMessageId.of(45);
-
- @Test
- public void shouldMatchBeanContract() {
- EqualsVerifier.forClass(MDN.class)
- .allFieldsShouldBeUsed()
- .verify();
- }
-
- @Test
- public void builderShouldReturnObjectWhenAllFieldsAreValid() {
- assertThat(
- MDN.builder()
- .disposition(DISPOSITION)
- .messageId(MESSAGE_ID)
- .reportingUA(REPORTING_UA)
- .subject(SUBJECT)
- .textBody(TEXT_BODY)
- .build())
- .isEqualTo(new MDN(MESSAGE_ID, SUBJECT, TEXT_BODY, REPORTING_UA, DISPOSITION));
- }
-
- @Test
- public void dispositionIsCompulsory() {
- assertThatThrownBy(() ->
- MDN.builder()
- .messageId(MESSAGE_ID)
- .reportingUA(REPORTING_UA)
- .subject(SUBJECT)
- .textBody(TEXT_BODY)
- .build())
- .isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- public void messageIdIsCompulsory() {
- assertThatThrownBy(() ->
- MDN.builder()
- .disposition(DISPOSITION)
- .reportingUA(REPORTING_UA)
- .subject(SUBJECT)
- .textBody(TEXT_BODY)
- .build())
- .isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- public void reportingUAIsCompulsory() {
- assertThatThrownBy(() ->
- MDN.builder()
- .disposition(DISPOSITION)
- .messageId(MESSAGE_ID)
- .subject(SUBJECT)
- .textBody(TEXT_BODY)
- .build())
- .isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- public void subjectIsCompulsory() {
- assertThatThrownBy(() ->
- MDN.builder()
- .disposition(DISPOSITION)
- .messageId(MESSAGE_ID)
- .reportingUA(REPORTING_UA)
- .textBody(TEXT_BODY)
- .build())
- .isInstanceOf(IllegalStateException.class);
- }
-
- @Test
- public void textBodyIsCompulsory() {
- assertThatThrownBy(() ->
- MDN.builder()
- .disposition(DISPOSITION)
- .messageId(MESSAGE_ID)
- .reportingUA(REPORTING_UA)
- .subject(SUBJECT)
- .build())
- .isInstanceOf(IllegalStateException.class);
- }
-
-}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/james-project/blob/9481435b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
----------------------------------------------------------------------
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
index 61cf8fb..4410adc 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/model/SetMessagesResponseTest.java
@@ -74,7 +74,8 @@ public class SetMessagesResponseTest {
ImmutableMap<MessageId, SetError> notUpdated = ImmutableMap.of(TestMessageId.of(4), SetError.builder().type("updated").build());
ImmutableMap<MessageId, SetError> notDestroyed = ImmutableMap.of(TestMessageId.of(5), SetError.builder().type("destroyed").build());
ImmutableMap<CreationMessageId, SetError> mdnNotSent = ImmutableMap.of(CreationMessageId.of("dead-beef-defec9"), SetError.builder().type("MDNNotSent").build());
- SetMessagesResponse expected = new SetMessagesResponse(null, null, null, created, updated, destroyed, notCreated, mdnNotSent, notUpdated, notDestroyed);
+ ImmutableMap<CreationMessageId, MessageId> mdnSent = ImmutableMap.of(CreationMessageId.of("dead-beef-defed0"), TestMessageId.of(12));
+ SetMessagesResponse expected = new SetMessagesResponse(null, null, null, created, mdnSent, updated, destroyed, notCreated, mdnNotSent, notUpdated, notDestroyed);
SetMessagesResponse setMessagesResponse = SetMessagesResponse.builder()
.created(created)
@@ -83,7 +84,8 @@ public class SetMessagesResponseTest {
.notCreated(notCreated)
.notUpdated(notUpdated)
.notDestroyed(notDestroyed)
- .MDNNotSent(mdnNotSent)
+ .mdnNotSent(mdnNotSent)
+ .mdnSent(mdnSent)
.build();
assertThat(setMessagesResponse).isEqualToComparingFieldByField(expected);
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org