You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/04/08 03:57:26 UTC
[james-project] branch master updated (4619763 -> 5ca0f79)
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git.
from 4619763 JAMES-3435 test for mailbox.read.strong.cst
new dc9633d JAMES-3537 Email/set create should be backed by the blob resolver
new d2912ec JAMES-3537 Refactoring: use a case class to get rid of a tuple3
new 5ca0f79 JAMES-3537 Email/set create should allow to attach emails
The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../rfc8621/contract/EmailSetMethodContract.scala | 109 +++++++++++++++++++++
.../org/apache/james/jmap/mail/EmailSet.scala | 101 +++++++++----------
.../jmap/method/EmailSetCreatePerformer.scala | 47 +++++----
3 files changed, 181 insertions(+), 76 deletions(-)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 01/03: JAMES-3537 Email/set create should be backed
by the blob resolver
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit dc9633d8a486f0878b12c824865a5cc2d2531616
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Apr 5 11:57:55 2021 +0700
JAMES-3537 Email/set create should be backed by the blob resolver
---
.../org/apache/james/jmap/mail/EmailSet.scala | 89 +++++++++-------------
.../jmap/method/EmailSetCreatePerformer.scala | 47 +++++++-----
2 files changed, 63 insertions(+), 73 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
index a9fb1e9..bebbb0a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -18,7 +18,6 @@
****************************************************************/
package org.apache.james.jmap.mail
-import java.io.IOException
import java.nio.charset.{StandardCharsets, Charset => NioCharset}
import java.util.Date
@@ -34,9 +33,9 @@ import org.apache.james.jmap.mail.Disposition.INLINE
import org.apache.james.jmap.mail.Email.Size
import org.apache.james.jmap.mail.EmailSet.{EmailCreationId, UnparsedMessageId}
import org.apache.james.jmap.method.WithAccountId
-import org.apache.james.mailbox.exception.AttachmentNotFoundException
-import org.apache.james.mailbox.model.{AttachmentId, AttachmentMetadata, Cid, MessageId}
-import org.apache.james.mailbox.{AttachmentContentLoader, AttachmentManager, MailboxSession}
+import org.apache.james.jmap.routes.{Blob, BlobResolvers}
+import org.apache.james.mailbox.MailboxSession
+import org.apache.james.mailbox.model.{Cid, MessageId}
import org.apache.james.mime4j.codec.EncoderUtil
import org.apache.james.mime4j.codec.EncoderUtil.Usage
import org.apache.james.mime4j.dom.field.{ContentIdField, ContentTypeField, FieldName}
@@ -46,10 +45,11 @@ import org.apache.james.mime4j.message.{BodyPartBuilder, MultipartBuilder}
import org.apache.james.mime4j.stream.{Field, NameValuePair, RawField}
import org.apache.james.util.html.HtmlTextExtractor
import play.api.libs.json.JsObject
+import reactor.core.scheduler.Schedulers
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
-import scala.util.{Right, Try}
+import scala.util.{Right, Try, Using}
object EmailSet {
type EmailCreationId = Id
@@ -124,10 +124,9 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
bodyValues: Option[Map[ClientPartId, ClientEmailBodyValue]],
specificHeaders: List[EmailHeader],
attachments: Option[List[Attachment]]) {
- def toMime4JMessage(attachmentManager: AttachmentManager,
- attachmentContentLoader: AttachmentContentLoader,
+ def toMime4JMessage(blobResolvers: BlobResolvers,
htmlTextExtractor: HtmlTextExtractor,
- mailboxSession: MailboxSession): Either[Exception, Message] =
+ mailboxSession: MailboxSession): Either[Throwable, Message] =
validateHtmlBody
.flatMap(maybeHtmlBody => validateTextBody.map((maybeHtmlBody, _)))
.flatMap {
@@ -148,7 +147,7 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
.flatMap(_ => {
specificHeaders.map(_.asField).foreach(builder.addField)
attachments.filter(_.nonEmpty).map(attachments =>
- createMultipartWithAttachments(maybeHtmlBody, maybeTextBody, attachments, attachmentManager, attachmentContentLoader, htmlTextExtractor, mailboxSession)
+ createMultipartWithAttachments(maybeHtmlBody, maybeTextBody, attachments, blobResolvers, htmlTextExtractor, mailboxSession)
.map(multipartBuilder => {
builder.setBody(multipartBuilder)
builder.build
@@ -177,19 +176,15 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
private def createMultipartWithAttachments(maybeHtmlBody: Option[String],
maybeTextBody: Option[String],
attachments: List[Attachment],
- attachmentManager: AttachmentManager,
- attachmentContentLoader: AttachmentContentLoader,
+ blobResolvers: BlobResolvers,
htmlTextExtractor: HtmlTextExtractor,
- mailboxSession: MailboxSession): Either[Exception, MultipartBuilder] = {
- val maybeAttachments: Either[Exception, List[(Attachment, AttachmentMetadata, Array[Byte])]] =
+ mailboxSession: MailboxSession): Either[Throwable, MultipartBuilder] = {
+ val maybeAttachments: Either[Throwable, List[(Attachment, Blob, Array[Byte])]] =
attachments
- .map(attachment => getAttachmentMetadata(attachment, attachmentManager, mailboxSession))
- .map(attachmentMetadataList => attachmentMetadataList
- .flatMap(attachmentAndMetadata => loadAttachment(attachmentAndMetadata._1, attachmentAndMetadata._2, attachmentContentLoader, mailboxSession)))
+ .map(loadWithMetadata(blobResolvers, mailboxSession))
.sequence
maybeAttachments.map(list => {
-
(list.filter(_._1.isInline), list.filter(!_._1.isInline)) match {
case (Nil, normalAttachments) => createMixedBody(maybeHtmlBody, maybeTextBody, normalAttachments, htmlTextExtractor)
case (inlineAttachments, Nil) => createRelatedBody(maybeHtmlBody, maybeTextBody, inlineAttachments, htmlTextExtractor)
@@ -198,54 +193,63 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
})
}
+ private def loadWithMetadata(blobResolvers: BlobResolvers, mailboxSession: MailboxSession)(attachment: Attachment): Either[Throwable, (Attachment, Blob, Array[Byte])] =
+ Try(blobResolvers.resolve(attachment.blobId, mailboxSession).subscribeOn(Schedulers.elastic()).block())
+ .toEither.flatMap(blob => load(blob).map(content => (attachment, blob, content)))
+
+ private def load(blob: Blob): Either[Throwable, Array[Byte]] =
+ Using(blob.content) {
+ _.readAllBytes()
+ }.toEither
+
private def createMixedRelatedBody(maybeHtmlBody: Option[String],
maybeTextBody: Option[String],
- inlineAttachments: List[(Attachment, AttachmentMetadata, Array[Byte])],
- normalAttachments: List[(Attachment, AttachmentMetadata, Array[Byte])],
+ inlineAttachments: List[(Attachment, Blob, Array[Byte])],
+ normalAttachments: List[(Attachment, Blob, Array[Byte])],
htmlTextExtractor: HtmlTextExtractor) = {
val mixedMultipartBuilder = MultipartBuilder.create(SubType.MIXED_SUBTYPE)
val relatedMultipartBuilder = MultipartBuilder.create(SubType.RELATED_SUBTYPE)
relatedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(createAlternativeBody(maybeHtmlBody, maybeTextBody, htmlTextExtractor).build))
inlineAttachments.foldLeft(relatedMultipartBuilder) {
- case (acc, (attachment, storedMetadata, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, storedMetadata, content))
+ case (acc, (attachment, blob, content)) =>
+ acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
acc
}
mixedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(relatedMultipartBuilder.build))
normalAttachments.foldLeft(mixedMultipartBuilder) {
- case (acc, (attachment, storedMetadata, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, storedMetadata, content))
+ case (acc, (attachment, blob, content)) =>
+ acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
acc
}
}
- private def createMixedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], normalAttachments: List[(Attachment, AttachmentMetadata, Array[Byte])], htmlTextExtractor: HtmlTextExtractor) = {
+ private def createMixedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], normalAttachments: List[(Attachment, Blob, Array[Byte])], htmlTextExtractor: HtmlTextExtractor) = {
val mixedMultipartBuilder = MultipartBuilder.create(SubType.MIXED_SUBTYPE)
mixedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(createAlternativeBody(maybeHtmlBody, maybeTextBody, htmlTextExtractor).build))
normalAttachments.foldLeft(mixedMultipartBuilder) {
- case (acc, (attachment, storedMetadata, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, storedMetadata, content))
+ case (acc, (attachment, blob, content)) =>
+ acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
acc
}
}
- private def createRelatedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], inlineAttachments: List[(Attachment, AttachmentMetadata, Array[Byte])], htmlTextExtractor: HtmlTextExtractor) = {
+ private def createRelatedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], inlineAttachments: List[(Attachment, Blob, Array[Byte])], htmlTextExtractor: HtmlTextExtractor) = {
val relatedMultipartBuilder = MultipartBuilder.create(SubType.RELATED_SUBTYPE)
relatedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(createAlternativeBody(maybeHtmlBody, maybeTextBody, htmlTextExtractor).build))
inlineAttachments.foldLeft(relatedMultipartBuilder) {
- case (acc, (attachment, storedMetadata, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, storedMetadata, content))
+ case (acc, (attachment, blob, content)) =>
+ acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
acc
}
relatedMultipartBuilder
}
- private def toBodypartBuilder(attachment: Attachment, storedMetadata: AttachmentMetadata, content: Array[Byte]) = {
+ private def toBodypartBuilder(attachment: Attachment, blob: Blob, content: Array[Byte]) = {
val bodypartBuilder = BodyPartBuilder.create()
bodypartBuilder.setBody(content, attachment.`type`.value)
- .setField(contentTypeField(attachment, storedMetadata))
+ .setField(contentTypeField(attachment, blob))
.setContentDisposition(attachment.disposition.getOrElse(Disposition.ATTACHMENT).value)
attachment.cid.map(_.asField).foreach(bodypartBuilder.addField)
attachment.location.map(_.asField).foreach(bodypartBuilder.addField)
@@ -253,8 +257,8 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
bodypartBuilder
}
- private def contentTypeField(attachment: Attachment, attachmentMetadata: AttachmentMetadata): ContentTypeField = {
- val typeAsField: ContentTypeField = attachmentMetadata.getType.asMime4J
+ private def contentTypeField(attachment: Attachment, blob: Blob): ContentTypeField = {
+ val typeAsField: ContentTypeField = blob.contentType.asMime4J
if (attachment.name.isDefined) {
Fields.contentType(typeAsField.getMimeType,
Map.newBuilder[String, String]
@@ -273,25 +277,6 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
.filter(!_._1.equals("name"))
.toMap
- private def getAttachmentMetadata(attachment: Attachment,
- attachmentManager: AttachmentManager,
- mailboxSession: MailboxSession): Either[AttachmentNotFoundException, (Attachment, AttachmentMetadata)] =
- Try(attachmentManager.getAttachment(AttachmentId.from(attachment.blobId.value.toString), mailboxSession))
- .fold(e => Left(new AttachmentNotFoundException(attachment.blobId.value.value, s"Attachment not found: ${attachment.blobId.value}", e)),
- attachmentMetadata => Right((attachment, attachmentMetadata)))
-
- private def loadAttachment(attachment: Attachment,
- attachmentMetadata: AttachmentMetadata,
- attachmentContentLoader: AttachmentContentLoader,
- mailboxSession: MailboxSession): Either[Exception, (Attachment, AttachmentMetadata, Array[Byte])] =
- Try(attachmentContentLoader.load(attachmentMetadata, mailboxSession))
- .toEither
- .fold(e => e match {
- case e: AttachmentNotFoundException => Left(new AttachmentNotFoundException(attachment.blobId.value.value, s"Attachment not found: ${attachment.blobId.value}", e))
- case e: IOException => Left(e)
- },
- inputStream => scala.Right((attachment, attachmentMetadata, inputStream.readAllBytes())))
-
def validateHtmlBody: Either[IllegalArgumentException, Option[String]] = htmlBody match {
case None => Right(None)
case Some(html :: Nil) if !html.`type`.value.equals("text/html") => Left(new IllegalArgumentException("Expecting htmlBody type to be text/html"))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
index 0bc684f..b05cdb5 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetCreatePerformer.scala
@@ -31,10 +31,12 @@ import org.apache.james.jmap.json.EmailSetSerializer
import org.apache.james.jmap.mail.EmailSet.EmailCreationId
import org.apache.james.jmap.mail.{BlobId, Email, EmailCreationRequest, EmailCreationResponse, EmailSetRequest}
import org.apache.james.jmap.method.EmailSetCreatePerformer.{CreationFailure, CreationResult, CreationResults, CreationSuccess}
+import org.apache.james.jmap.routes.{BlobNotFoundException, BlobResolvers}
import org.apache.james.mailbox.MessageManager.AppendCommand
-import org.apache.james.mailbox.exception.{AttachmentNotFoundException, MailboxNotFoundException}
+import org.apache.james.mailbox.exception.MailboxNotFoundException
import org.apache.james.mailbox.model.MailboxId
-import org.apache.james.mailbox.{AttachmentContentLoader, AttachmentManager, MailboxManager, MailboxSession}
+import org.apache.james.mailbox.{MailboxManager, MailboxSession}
+import org.apache.james.mime4j.dom.Message
import org.apache.james.util.html.HtmlTextExtractor
import reactor.core.scala.publisher.{SFlux, SMono}
import reactor.core.scheduler.Schedulers
@@ -62,7 +64,7 @@ object EmailSetCreatePerformer {
case class CreationFailure(clientId: EmailCreationId, e: Throwable) extends CreationResult {
def asMessageSetError: SetError = e match {
case e: MailboxNotFoundException => SetError.notFound(SetErrorDescription("Mailbox " + e.getMessage))
- case e: AttachmentNotFoundException => SetError.invalidArguments(SetErrorDescription(s"${e.getMessage}"), Some(Properties("attachments")))
+ case e: BlobNotFoundException => SetError.invalidArguments(SetErrorDescription(s"Attachment not found: ${e.blobId.value}"), Some(Properties("attachments")))
case e: IllegalArgumentException => SetError.invalidArguments(SetErrorDescription(e.getMessage))
case _ => SetError.serverFail(SetErrorDescription(e.getMessage))
}
@@ -70,8 +72,7 @@ object EmailSetCreatePerformer {
}
class EmailSetCreatePerformer @Inject()(serializer: EmailSetSerializer,
- attachmentManager: AttachmentManager,
- attachmentContentLoader: AttachmentContentLoader,
+ blobResolvers: BlobResolvers,
htmlTextExtractor: HtmlTextExtractor,
mailboxManager: MailboxManager) {
@@ -89,22 +90,26 @@ class EmailSetCreatePerformer @Inject()(serializer: EmailSetSerializer,
if (mailboxIds.size != 1) {
SMono.just(CreationFailure(clientId, new IllegalArgumentException("mailboxIds need to have size 1")))
} else {
- request.toMime4JMessage(attachmentManager, attachmentContentLoader, htmlTextExtractor, mailboxSession)
- .fold(e => SMono.just(CreationFailure(clientId, e)),
- message => SMono.fromCallable[CreationResult](() => {
- val appendResult = mailboxManager.getMailbox(mailboxIds.head, mailboxSession)
- .appendMessage(AppendCommand.builder()
- .recent()
- .withFlags(request.keywords.map(_.asFlags).getOrElse(new Flags()))
- .withInternalDate(Date.from(request.receivedAt.getOrElse(UTCDate(ZonedDateTime.now())).asUTC.toInstant))
- .build(message),
- mailboxSession)
-
- val blobId: Option[BlobId] = BlobId.of(appendResult.getId.getMessageId).toOption
- CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId, blobId, blobId, Email.sanitizeSize(appendResult.getSize)))
- })
- .subscribeOn(Schedulers.elastic())
- .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e))))
+ SMono.fromCallable(() => request.toMime4JMessage(blobResolvers, htmlTextExtractor, mailboxSession))
+ .flatMap(either => either.fold(e => SMono.just(CreationFailure(clientId, e)),
+ message => SMono.fromCallable[CreationResult](() => append(clientId, asAppendCommand(request, message), mailboxSession, mailboxIds))))
+ .onErrorResume(e => SMono.just[CreationResult](CreationFailure(clientId, e)))
+ .subscribeOn(Schedulers.elastic())
}
}
+
+ private def append(clientId: EmailCreationId, appendCommand: AppendCommand, mailboxSession: MailboxSession, mailboxIds: List[MailboxId]): CreationSuccess = {
+ val appendResult = mailboxManager.getMailbox(mailboxIds.head, mailboxSession)
+ .appendMessage(appendCommand, mailboxSession)
+
+ val blobId: Option[BlobId] = BlobId.of(appendResult.getId.getMessageId).toOption
+ CreationSuccess(clientId, EmailCreationResponse(appendResult.getId.getMessageId, blobId, blobId, Email.sanitizeSize(appendResult.getSize)))
+ }
+
+ private def asAppendCommand(request: EmailCreationRequest, message: Message): AppendCommand =
+ AppendCommand.builder()
+ .recent()
+ .withFlags(request.keywords.map(_.asFlags).getOrElse(new Flags()))
+ .withInternalDate(Date.from(request.receivedAt.getOrElse(UTCDate(ZonedDateTime.now())).asUTC.toInstant))
+ .build(message)
}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 02/03: JAMES-3537 Refactoring: use a case class to
get rid of a tuple3
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit d2912ece655de976db8287466bf70b7b4d4965a1
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Apr 5 13:26:29 2021 +0700
JAMES-3537 Refactoring: use a case class to get rid of a tuple3
---
.../org/apache/james/jmap/mail/EmailSet.scala | 44 ++++++++++++----------
1 file changed, 25 insertions(+), 19 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
index bebbb0a..7505f72 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -179,13 +179,13 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
blobResolvers: BlobResolvers,
htmlTextExtractor: HtmlTextExtractor,
mailboxSession: MailboxSession): Either[Throwable, MultipartBuilder] = {
- val maybeAttachments: Either[Throwable, List[(Attachment, Blob, Array[Byte])]] =
+ val maybeAttachments: Either[Throwable, List[LoadedAttachment]] =
attachments
.map(loadWithMetadata(blobResolvers, mailboxSession))
.sequence
maybeAttachments.map(list => {
- (list.filter(_._1.isInline), list.filter(!_._1.isInline)) match {
+ (list.filter(_.isInline), list.filter(!_.isInline)) match {
case (Nil, normalAttachments) => createMixedBody(maybeHtmlBody, maybeTextBody, normalAttachments, htmlTextExtractor)
case (inlineAttachments, Nil) => createRelatedBody(maybeHtmlBody, maybeTextBody, inlineAttachments, htmlTextExtractor)
case (inlineAttachments, normalAttachments) => createMixedRelatedBody(maybeHtmlBody, maybeTextBody, inlineAttachments, normalAttachments, htmlTextExtractor)
@@ -193,9 +193,9 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
})
}
- private def loadWithMetadata(blobResolvers: BlobResolvers, mailboxSession: MailboxSession)(attachment: Attachment): Either[Throwable, (Attachment, Blob, Array[Byte])] =
+ private def loadWithMetadata(blobResolvers: BlobResolvers, mailboxSession: MailboxSession)(attachment: Attachment): Either[Throwable, LoadedAttachment] =
Try(blobResolvers.resolve(attachment.blobId, mailboxSession).subscribeOn(Schedulers.elastic()).block())
- .toEither.flatMap(blob => load(blob).map(content => (attachment, blob, content)))
+ .toEither.flatMap(blob => load(blob).map(content => LoadedAttachment(attachment, blob, content)))
private def load(blob: Blob): Either[Throwable, Array[Byte]] =
Using(blob.content) {
@@ -204,51 +204,53 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
private def createMixedRelatedBody(maybeHtmlBody: Option[String],
maybeTextBody: Option[String],
- inlineAttachments: List[(Attachment, Blob, Array[Byte])],
- normalAttachments: List[(Attachment, Blob, Array[Byte])],
- htmlTextExtractor: HtmlTextExtractor) = {
+ inlineAttachments: List[LoadedAttachment],
+ normalAttachments: List[LoadedAttachment],
+ htmlTextExtractor: HtmlTextExtractor): MultipartBuilder = {
val mixedMultipartBuilder = MultipartBuilder.create(SubType.MIXED_SUBTYPE)
val relatedMultipartBuilder = MultipartBuilder.create(SubType.RELATED_SUBTYPE)
relatedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(createAlternativeBody(maybeHtmlBody, maybeTextBody, htmlTextExtractor).build))
inlineAttachments.foldLeft(relatedMultipartBuilder) {
- case (acc, (attachment, blob, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
+ case (acc, loadedAttachment) =>
+ acc.addBodyPart(toBodypartBuilder(loadedAttachment))
acc
}
mixedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(relatedMultipartBuilder.build))
normalAttachments.foldLeft(mixedMultipartBuilder) {
- case (acc, (attachment, blob, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
+ case (acc, loadedAttachment) =>
+ acc.addBodyPart(toBodypartBuilder(loadedAttachment))
acc
}
}
- private def createMixedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], normalAttachments: List[(Attachment, Blob, Array[Byte])], htmlTextExtractor: HtmlTextExtractor) = {
+ private def createMixedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], normalAttachments: List[LoadedAttachment], htmlTextExtractor: HtmlTextExtractor) = {
val mixedMultipartBuilder = MultipartBuilder.create(SubType.MIXED_SUBTYPE)
mixedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(createAlternativeBody(maybeHtmlBody, maybeTextBody, htmlTextExtractor).build))
normalAttachments.foldLeft(mixedMultipartBuilder) {
- case (acc, (attachment, blob, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
+ case (acc, loadedAttachment) =>
+ acc.addBodyPart(toBodypartBuilder(loadedAttachment))
acc
}
}
- private def createRelatedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], inlineAttachments: List[(Attachment, Blob, Array[Byte])], htmlTextExtractor: HtmlTextExtractor) = {
+ private def createRelatedBody(maybeHtmlBody: Option[String], maybeTextBody: Option[String], inlineAttachments: List[LoadedAttachment], htmlTextExtractor: HtmlTextExtractor) = {
val relatedMultipartBuilder = MultipartBuilder.create(SubType.RELATED_SUBTYPE)
relatedMultipartBuilder.addBodyPart(BodyPartBuilder.create().setBody(createAlternativeBody(maybeHtmlBody, maybeTextBody, htmlTextExtractor).build))
inlineAttachments.foldLeft(relatedMultipartBuilder) {
- case (acc, (attachment, blob, content)) =>
- acc.addBodyPart(toBodypartBuilder(attachment, blob, content))
+ case (acc, loadedAttachment) =>
+ acc.addBodyPart(toBodypartBuilder(loadedAttachment))
acc
}
relatedMultipartBuilder
}
- private def toBodypartBuilder(attachment: Attachment, blob: Blob, content: Array[Byte]) = {
+ private def toBodypartBuilder(loadedAttachment: LoadedAttachment) = {
val bodypartBuilder = BodyPartBuilder.create()
- bodypartBuilder.setBody(content, attachment.`type`.value)
+ val attachment = loadedAttachment.attachment
+ val blob = loadedAttachment.blob
+ bodypartBuilder.setBody(loadedAttachment.content, attachment.`type`.value)
.setField(contentTypeField(attachment, blob))
.setContentDisposition(attachment.disposition.getOrElse(Disposition.ATTACHMENT).value)
attachment.cid.map(_.asField).foreach(bodypartBuilder.addField)
@@ -315,6 +317,10 @@ case class EmailCreationRequest(mailboxIds: MailboxIds,
}
}
+case class LoadedAttachment(attachment: Attachment, blob: Blob, content: Array[Byte]) {
+ def isInline: Boolean = attachment.isInline
+}
+
case class DestroyIds(value: Seq[UnparsedMessageId])
case class EmailSetRequest(accountId: AccountId,
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org
[james-project] 03/03: JAMES-3537 Email/set create should allow to
attach emails
Posted by bt...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 5ca0f7958d0a67f936342fceb6ea5ede709ecc25
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Mon Apr 5 15:26:06 2021 +0700
JAMES-3537 Email/set create should allow to attach emails
---
.../rfc8621/contract/EmailSetMethodContract.scala | 109 +++++++++++++++++++++
1 file changed, 109 insertions(+)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
index a7096e6..4c33a7b 100644
--- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -3729,6 +3729,115 @@ trait EmailSetMethodContract {
}
@Test
+ def createShouldSupportAttachedMessages(server: GuiceJamesServer): Unit = {
+ val bobPath = MailboxPath.inbox(BOB)
+ val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
+ val message: Message = Message.Builder
+ .of
+ .setSubject("test")
+ .setSender(ANDRE.asString())
+ .setFrom(ANDRE.asString())
+ .setSubject("I'm happy to be attached")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ val attachedMessageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
+ .appendMessage(BOB.asString, bobPath, AppendCommand.from(message))
+ .getMessageId
+
+ val blobId: String = attachedMessageId.serialize()
+
+ val request =
+ s"""{
+ | "using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "$ACCOUNT_ID",
+ | "create": {
+ | "aaaaaa": {
+ | "mailboxIds": {
+ | "${mailboxId.serialize}": true
+ | },
+ | "subject": "World domination",
+ | "attachments": [
+ | {
+ | "blobId": "$blobId",
+ | "charset":"us-ascii",
+ | "disposition": "attachment",
+ | "type":"message/rfc822"
+ | }
+ | ]
+ | }
+ | }
+ | }, "c1"],
+ | ["Email/get",
+ | {
+ | "accountId": "$ACCOUNT_ID",
+ | "ids": ["#aaaaaa"],
+ | "properties": ["mailboxIds", "subject", "attachments"],
+ | "bodyProperties": ["partId", "blobId", "size", "type", "charset", "disposition"]
+ | },
+ | "c2"]
+ | ]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ val responseAsJson = Json.parse(response)
+ .\("methodResponses")
+ .\(0).\(1)
+ .\("created")
+ .\("aaaaaa")
+
+ val messageId = responseAsJson
+ .\("id")
+ .get.asInstanceOf[JsString].value
+ val size = responseAsJson
+ .\("size")
+ .get.asInstanceOf[JsNumber].value
+
+ assertThatJson(response)
+ .inPath("methodResponses[0][1].created.aaaaaa")
+ .isEqualTo(
+ s"""{
+ | "id": "$messageId",
+ | "blobId": "$messageId",
+ | "threadId": "$messageId",
+ | "size": $size
+ |}""".stripMargin)
+
+ assertThatJson(response)
+ .inPath(s"methodResponses[1][1].list[0]")
+ .isEqualTo(
+ s"""{
+ | "id": "$messageId",
+ | "mailboxIds": {
+ | "${mailboxId.serialize}": true
+ | },
+ | "subject": "World domination",
+ | "attachments": [
+ | {
+ | "partId": "4",
+ | "blobId": "${messageId}_4",
+ | "size": 155,
+ | "type": "message/rfc822",
+ | "charset": "us-ascii",
+ | "disposition": "attachment"
+ | }
+ | ]
+ |}""".stripMargin)
+ }
+
+ @Test
def createShouldFailWhenAttachmentNotFound(server: GuiceJamesServer): Unit = {
val bobPath = MailboxPath.inbox(BOB)
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(bobPath)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org