JAMES-2214 Introduce helper class for saving and sending emails with JMAP
Project: http://git-wip-us.apache.org/repos/asf/james-project/repo Commit: http://git-wip-us.apache.org/repos/asf/james-project/commit/4dbe064d Tree: http://git-wip-us.apache.org/repos/asf/james-project/tree/4dbe064d Diff: http://git-wip-us.apache.org/repos/asf/james-project/diff/4dbe064d Branch: refs/heads/master Commit: 4dbe064d2e516804f2a058313f83fd7ce078f838 Parents: b539779 Author: benwa <[email protected]> Authored: Mon Nov 13 13:22:44 2017 +0700 Committer: benwa <[email protected]> Committed: Wed Nov 15 17:58:15 2017 +0700 ---------------------------------------------------------------------- .../james/jmap/methods/MessageAppender.java | 113 ++++++++++++++++++ .../james/jmap/methods/MessageSender.java | 70 +++++++++++ .../methods/SetMessagesCreationProcessor.java | 117 ++----------------- .../SetMessagesCreationProcessorTest.java | 20 +++- 4 files changed, 211 insertions(+), 109 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/james-project/blob/4dbe064d/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 new file mode 100644 index 0000000..4b1fad6 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageAppender.java @@ -0,0 +1,113 @@ +/**************************************************************** + * 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.Date; +import java.util.Optional; + +import javax.inject.Inject; +import javax.mail.util.SharedByteArrayInputStream; + +import org.apache.james.jmap.model.Attachment; +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.MailboxSession; +import org.apache.james.mailbox.MessageManager; +import org.apache.james.mailbox.exception.AttachmentNotFoundException; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.james.mailbox.model.AttachmentId; +import org.apache.james.mailbox.model.Cid; +import org.apache.james.mailbox.model.ComposedMessageId; +import org.apache.james.mailbox.model.MessageAttachment; +import org.apache.james.util.OptionalUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.fge.lambdas.Throwing; +import com.github.fge.lambdas.functions.ThrowingFunction; +import com.github.steveash.guavate.Guavate; +import com.google.common.collect.ImmutableList; + +public class MessageAppender { + private static final Logger LOGGER = LoggerFactory.getLogger(MessageAppender.class); + + private final AttachmentManager attachmentManager; + private final MIMEMessageConverter mimeMessageConverter; + + @Inject + public MessageAppender(AttachmentManager attachmentManager, MIMEMessageConverter mimeMessageConverter) { + this.attachmentManager = attachmentManager; + this.mimeMessageConverter = mimeMessageConverter; + } + + public MessageFactory.MetaDataWithContent createMessageInMailbox(ValueWithId.MessageWithId.CreationMessageEntry createdEntry, + MessageManager mailbox, + MailboxSession session) throws MailboxException { + ImmutableList<MessageAttachment> messageAttachments = getMessageAttachments(session, createdEntry.getValue().getAttachments()); + byte[] messageContent = mimeMessageConverter.convert(createdEntry, messageAttachments); + SharedByteArrayInputStream content = new SharedByteArrayInputStream(messageContent); + Date internalDate = Date.from(createdEntry.getValue().getDate().toInstant()); + + Keywords keywords = createdEntry.getValue() + .getKeywords() + .orElse(Keywords.DEFAULT_VALUE); + boolean notRecent = false; + + ComposedMessageId message = mailbox.appendMessage(content, internalDate, session, notRecent, keywords.asFlags()); + + return MessageFactory.MetaDataWithContent.builder() + .uid(message.getUid()) + .keywords(keywords) + .internalDate(internalDate.toInstant()) + .sharedContent(content) + .size(messageContent.length) + .attachments(messageAttachments) + .mailboxId(mailbox.getId()) + .messageId(message.getMessageId()) + .build(); + } + + private ImmutableList<MessageAttachment> getMessageAttachments(MailboxSession session, ImmutableList<Attachment> attachments) throws MailboxException { + ThrowingFunction<Attachment, Optional<MessageAttachment>> toMessageAttachment = att -> messageAttachment(session, att); + return attachments.stream() + .map(Throwing.function(toMessageAttachment).sneakyThrow()) + .flatMap(OptionalUtils::toStream) + .collect(Guavate.toImmutableList()); + } + + private Optional<MessageAttachment> messageAttachment(MailboxSession session, Attachment attachment) throws MailboxException { + try { + return Optional.of(MessageAttachment.builder() + .attachment(attachmentManager.getAttachment(AttachmentId.from(attachment.getBlobId().getRawValue()), session)) + .name(attachment.getName().orElse(null)) + .cid(attachment.getCid().map(Cid::from).orElse(null)) + .isInline(attachment.isIsInline()) + .build()); + } catch (AttachmentNotFoundException e) { + // should not happen (checked before) + LOGGER.error(String.format("Attachment %s not found", attachment.getBlobId()), e); + return Optional.empty(); + } catch (IllegalStateException e) { + LOGGER.error(String.format("Attachment %s is not well-formed", attachment.getBlobId()), e); + return Optional.empty(); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4dbe064d/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.java new file mode 100644 index 0000000..dd94618 --- /dev/null +++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/methods/MessageSender.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.methods; + +import java.io.IOException; +import java.util.List; + +import javax.inject.Inject; +import javax.mail.MessagingException; + +import org.apache.james.jmap.model.CreationMessageId; +import org.apache.james.jmap.model.Message; +import org.apache.james.jmap.model.MessageFactory; +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.lifecycle.api.LifecycleUtil; +import org.apache.james.mailbox.MailboxSession; +import org.apache.james.mailbox.exception.MailboxException; +import org.apache.mailet.Mail; + +import com.google.common.collect.ImmutableList; + +public class MessageSender { + private final MailSpool mailSpool; + private final MailFactory mailFactory; + + @Inject + public MessageSender(MailSpool mailSpool, MailFactory mailFactory) { + this.mailSpool = mailSpool; + this.mailFactory = mailFactory; + } + + public void sendMessage(Message jmapMessage, + MessageFactory.MetaDataWithContent message, + MailboxSession session) throws MailboxException, MessagingException { + Mail mail = buildMessage(message, jmapMessage); + try { + MailMetadata metadata = new MailMetadata(jmapMessage.getId(), session.getUser().getUserName()); + mailSpool.send(mail, metadata); + } finally { + LifecycleUtil.dispose(mail); + } + } + + private Mail buildMessage(MessageFactory.MetaDataWithContent message, Message jmapMessage) throws MessagingException { + try { + return mailFactory.build(message, jmapMessage); + } catch (IOException e) { + throw new MessagingException("error building message to send", e); + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/4dbe064d/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 8c482e3..8fe1161 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 @@ -21,8 +21,6 @@ package org.apache.james.jmap.methods; import static org.apache.james.jmap.methods.Method.JMAP_PREFIX; -import java.io.IOException; -import java.util.Date; import java.util.List; import java.util.Optional; import java.util.Set; @@ -30,7 +28,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.mail.MessagingException; -import javax.mail.util.SharedByteArrayInputStream; import org.apache.james.jmap.exceptions.AttachmentsNotFoundException; import org.apache.james.jmap.methods.ValueWithId.CreationMessageEntry; @@ -38,8 +35,6 @@ import org.apache.james.jmap.methods.ValueWithId.MessageWithId; import org.apache.james.jmap.model.Attachment; import org.apache.james.jmap.model.BlobId; import org.apache.james.jmap.model.CreationMessage; -import org.apache.james.jmap.model.CreationMessageId; -import org.apache.james.jmap.model.Keywords; import org.apache.james.jmap.model.Message; import org.apache.james.jmap.model.MessageFactory; import org.apache.james.jmap.model.MessageFactory.MetaDataWithContent; @@ -51,11 +46,7 @@ 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.jmap.utils.SystemMailboxesProvider; -import org.apache.james.lifecycle.api.LifecycleUtil; import org.apache.james.mailbox.AttachmentManager; import org.apache.james.mailbox.MailboxManager; import org.apache.james.mailbox.MailboxSession; @@ -64,19 +55,13 @@ import org.apache.james.mailbox.exception.AttachmentNotFoundException; import org.apache.james.mailbox.exception.MailboxException; import org.apache.james.mailbox.exception.MailboxNotFoundException; import org.apache.james.mailbox.model.AttachmentId; -import org.apache.james.mailbox.model.Cid; -import org.apache.james.mailbox.model.ComposedMessageId; import org.apache.james.mailbox.model.MailboxId; -import org.apache.james.mailbox.model.MessageAttachment; import org.apache.james.metrics.api.MetricFactory; import org.apache.james.metrics.api.TimeMetric; -import org.apache.james.util.OptionalUtils; -import org.apache.mailet.Mail; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.github.fge.lambdas.Throwing; -import com.github.fge.lambdas.functions.ThrowingFunction; import com.github.fge.lambdas.predicates.ThrowingPredicate; import com.github.steveash.guavate.Guavate; import com.google.common.annotations.VisibleForTesting; @@ -88,35 +73,29 @@ import com.google.common.collect.ImmutableList; public class SetMessagesCreationProcessor implements SetMessagesProcessor { private static final Logger LOG = LoggerFactory.getLogger(SetMailboxesCreationProcessor.class); - private final MIMEMessageConverter mimeMessageConverter; - private final MailSpool mailSpool; - private final MailFactory mailFactory; private final MessageFactory messageFactory; private final SystemMailboxesProvider systemMailboxesProvider; private final AttachmentManager attachmentManager; private final MetricFactory metricFactory; private final MailboxManager mailboxManager; private final MailboxId.Factory mailboxIdFactory; + private final MessageAppender messageAppender; + private final MessageSender messageSender; @VisibleForTesting @Inject - SetMessagesCreationProcessor(MIMEMessageConverter mimeMessageConverter, - MailSpool mailSpool, - MailFactory mailFactory, - MessageFactory messageFactory, - SystemMailboxesProvider systemMailboxesProvider, - AttachmentManager attachmentManager, + SetMessagesCreationProcessor(MessageFactory messageFactory, SystemMailboxesProvider systemMailboxesProvider, + AttachmentManager attachmentManager, MetricFactory metricFactory, MailboxManager mailboxManager, - MailboxId.Factory mailboxIdFactory) { - this.mimeMessageConverter = mimeMessageConverter; - this.mailSpool = mailSpool; - this.mailFactory = mailFactory; + MailboxId.Factory mailboxIdFactory, MessageAppender messageAppender, MessageSender messageSender) { this.messageFactory = messageFactory; this.systemMailboxesProvider = systemMailboxesProvider; this.attachmentManager = attachmentManager; this.metricFactory = metricFactory; this.mailboxManager = mailboxManager; this.mailboxIdFactory = mailboxIdFactory; + this.messageAppender = messageAppender; + this.messageSender = messageSender; } @Override @@ -279,8 +258,10 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { if (!isRequestForSending(entry.getValue(), session)) { throw new IllegalStateException("Messages for everything but outbox should have been filtered earlier"); } - MetaDataWithContent newMessage = createMessageInOutbox(entry, outbox, session); - return sendMessage(entry.getCreationId(), newMessage, session); + MetaDataWithContent newMessage = messageAppender.createMessageInMailbox(entry, outbox, session); + Message jmapMessage = messageFactory.fromMetaDataWithContent(newMessage); + messageSender.sendMessage(jmapMessage, newMessage, session); + return new ValueWithId.MessageWithId(entry.getCreationId(), jmapMessage); } private boolean isAppendToMailboxWithRole(Role role, CreationMessage entry, MailboxSession mailboxSession) throws MailboxException { @@ -318,81 +299,5 @@ public class SetMessagesCreationProcessor implements SetMessagesProcessor { private boolean isRequestForSending(CreationMessage creationMessage, MailboxSession session) throws MailboxException { return isAppendToMailboxWithRole(Role.OUTBOX, creationMessage, session); } - - private MetaDataWithContent createMessageInOutbox(MessageWithId.CreationMessageEntry createdEntry, - MessageManager outbox, - MailboxSession session) throws MailboxException { - ImmutableList<MessageAttachment> messageAttachments = getMessageAttachments(session, createdEntry.getValue().getAttachments()); - byte[] messageContent = mimeMessageConverter.convert(createdEntry, messageAttachments); - SharedByteArrayInputStream content = new SharedByteArrayInputStream(messageContent); - Date internalDate = Date.from(createdEntry.getValue().getDate().toInstant()); - - Keywords keywords = createdEntry.getValue() - .getKeywords() - .orElse(Keywords.DEFAULT_VALUE); - boolean notRecent = false; - - ComposedMessageId message = outbox.appendMessage(content, internalDate, session, notRecent, keywords.asFlags()); - - return MetaDataWithContent.builder() - .uid(message.getUid()) - .keywords(keywords) - .internalDate(internalDate.toInstant()) - .sharedContent(content) - .size(messageContent.length) - .attachments(messageAttachments) - .mailboxId(outbox.getId()) - .messageId(message.getMessageId()) - .build(); - } - private ImmutableList<MessageAttachment> getMessageAttachments(MailboxSession session, ImmutableList<Attachment> attachments) throws MailboxException { - ThrowingFunction<Attachment, Optional<MessageAttachment>> toMessageAttachment = att -> messageAttachment(session, att); - return attachments.stream() - .map(Throwing.function(toMessageAttachment).sneakyThrow()) - .flatMap(OptionalUtils::toStream) - .collect(Guavate.toImmutableList()); - } - - private Optional<MessageAttachment> messageAttachment(MailboxSession session, Attachment attachment) throws MailboxException { - try { - return Optional.of(MessageAttachment.builder() - .attachment(attachmentManager.getAttachment(AttachmentId.from(attachment.getBlobId().getRawValue()), session)) - .name(attachment.getName().orElse(null)) - .cid(attachment.getCid().map(Cid::from).orElse(null)) - .isInline(attachment.isIsInline()) - .build()); - } catch (AttachmentNotFoundException e) { - // should not happen (checked before) - LOG.error(String.format("Attachment %s not found", attachment.getBlobId()), e); - return Optional.empty(); - } catch (IllegalStateException e) { - LOG.error(String.format("Attachment %s is not well-formed", attachment.getBlobId()), e); - return Optional.empty(); - } - } - - private MessageWithId sendMessage(CreationMessageId creationId, MetaDataWithContent message, MailboxSession session) throws MailboxException, MessagingException { - Message jmapMessage = messageFactory.fromMetaDataWithContent(message); - sendMessage(message, jmapMessage, session); - return new MessageWithId(creationId, jmapMessage); - } - - private void sendMessage(MetaDataWithContent message, Message jmapMessage, MailboxSession session) throws MessagingException { - Mail mail = buildMessage(message, jmapMessage); - try { - MailMetadata metadata = new MailMetadata(jmapMessage.getId(), session.getUser().getUserName()); - mailSpool.send(mail, metadata); - } finally { - LifecycleUtil.dispose(mail); - } - } - - private Mail buildMessage(MetaDataWithContent message, Message jmapMessage) throws MessagingException { - try { - return mailFactory.build(message, jmapMessage); - } catch (IOException e) { - throw new MessagingException("error building message to send", e); - } - } } http://git-wip-us.apache.org/repos/asf/james-project/blob/4dbe064d/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java ---------------------------------------------------------------------- diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java index 8618b11..25ece63 100644 --- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java +++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/methods/SetMessagesCreationProcessorTest.java @@ -127,6 +127,8 @@ public class SetMessagesCreationProcessorTest { @Rule public ExpectedException expectedException = ExpectedException.none(); + private MessageAppender messageAppender; + private MessageSender messageSender; @Before public void setUp() throws MailboxException { @@ -146,7 +148,16 @@ public class SetMessagesCreationProcessorTest { fakeSystemMailboxesProvider = new TestSystemMailboxesProvider(() -> optionalOutbox, () -> optionalDrafts); session = new MockMailboxSession(USER); mimeMessageConverter = new MIMEMessageConverter(); - sut = new SetMessagesCreationProcessor(mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider, mockedAttachmentManager, new NoopMetricFactory(), mockedMailboxManager, mockedMailboxIdFactory); + messageAppender = new MessageAppender(mockedAttachmentManager, mimeMessageConverter); + messageSender = new MessageSender(mockedMailSpool, mockedMailFactory); + sut = new SetMessagesCreationProcessor(messageFactory, + fakeSystemMailboxesProvider, + mockedAttachmentManager, + new NoopMetricFactory(), + mockedMailboxManager, + mockedMailboxIdFactory, + messageAppender, + messageSender); outbox = mock(MessageManager.class); when(mockedMailboxIdFactory.fromString(OUTBOX_ID.serialize())) @@ -230,7 +241,7 @@ public class SetMessagesCreationProcessorTest { @Test public void processShouldReturnNonEmptyCreatedWhenRequestHasNonEmptyCreate() throws MailboxException { // Given - sut = new SetMessagesCreationProcessor(mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, fakeSystemMailboxesProvider, mockedAttachmentManager, new NoopMetricFactory(), mockedMailboxManager, mockedMailboxIdFactory); + sut = new SetMessagesCreationProcessor(messageFactory, fakeSystemMailboxesProvider, mockedAttachmentManager, new NoopMetricFactory(), mockedMailboxManager, mockedMailboxIdFactory, messageAppender, messageSender); // When SetMessagesResponse result = sut.process(createMessageInOutbox, session); @@ -245,7 +256,10 @@ public class SetMessagesCreationProcessorTest { public void processShouldReturnErrorWhenOutboxNotFound() { // Given TestSystemMailboxesProvider doNotProvideOutbox = new TestSystemMailboxesProvider(Optional::empty, () -> optionalDrafts); - SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(mimeMessageConverter, mockedMailSpool, mockedMailFactory, messageFactory, doNotProvideOutbox, mockedAttachmentManager, new NoopMetricFactory(), mockedMailboxManager, mockedMailboxIdFactory); + SetMessagesCreationProcessor sut = new SetMessagesCreationProcessor(messageFactory, doNotProvideOutbox, + mockedAttachmentManager, new NoopMetricFactory(), mockedMailboxManager, mockedMailboxIdFactory, + messageAppender, + messageSender); // When SetMessagesResponse actual = sut.process(createMessageInOutbox, session); --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
