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 2020/09/08 03:42:56 UTC
[james-project] 02/19: JAMES-3369 EmailBodyPart traversal
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 7f60fecdb24975e603de6eaf8c81fd53b196fd45
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Sep 4 11:49:02 2020 +0700
JAMES-3369 EmailBodyPart traversal
---
server/protocols/jmap-rfc-8621/pom.xml | 5 ++
.../org/apache/james/jmap/mail/EmailBodyPart.scala | 99 +++++++++++++++++++++-
.../org/apache/james/jmap/mail/EmailHeader.scala | 6 ++
3 files changed, 107 insertions(+), 3 deletions(-)
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index bfa2e7f..617bee6 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -135,6 +135,11 @@
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.typelevel</groupId>
+ <artifactId>cats-core_${scala.base}</artifactId>
+ <version>2.2.0</version>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
index 767d7db..323da6e 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
@@ -19,11 +19,102 @@
package org.apache.james.jmap.mail
+import java.io.OutputStream
+
+import cats.implicits._
+import com.google.common.io.CountingOutputStream
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.auto._
import eu.timepit.refined.numeric.NonNegative
+import eu.timepit.refined.refineV
import org.apache.james.jmap.mail.Email.Size
+import org.apache.james.jmap.mail.PartId.PartIdValue
+import org.apache.james.mailbox.model.MessageId
+import org.apache.james.mime4j.dom.{Entity, Message, Multipart}
+import org.apache.james.mime4j.message.DefaultMessageWriter
+
+import scala.jdk.CollectionConverters._
+import scala.util.{Failure, Success, Try}
+
+object PartId {
+ type PartIdValue = Int Refined NonNegative
+}
+
+case class PartId(value: PartIdValue) {
+ def serialize: String = value.toString
+
+ def next: PartId = refineV[NonNegative](value + 1) match {
+ case scala.Right(incrementedValue) => PartId(incrementedValue)
+ case Left(e) => throw new IllegalArgumentException(e)
+ }
+}
+
+object EmailBodyPart {
+ def of(messageId: MessageId, message: Message): Try[EmailBodyPart] =
+ of(messageId, PartId(1), message).map(_._1)
-case class PartId(value: NonNegative) {
- def serialize(): String = value.toString
+ def of(messageId: MessageId, partId: PartId, entity: Entity): Try[(EmailBodyPart, PartId)] =
+ entity.getBody match {
+ case multipart: Multipart =>
+ val scanResults: Try[List[(Option[EmailBodyPart], PartId)]] = multipart.getBodyParts
+ .asScala.toList
+ .scanLeft[Try[(Option[EmailBodyPart], PartId)]](Success((None, partId)))(traverse(messageId))
+ .sequence
+ val highestPartIdValidation: Try[PartId] = scanResults.map(list => list.map(_._2).reverse.headOption.getOrElse(partId))
+ val childrenValidation: Try[List[EmailBodyPart]] = scanResults.map(list => list.flatMap(_._1))
+
+ zip(childrenValidation, highestPartIdValidation)
+ .flatMap({
+ case (children, highestPartId) => of(messageId, partId, entity, Some(children))
+ .map(part => (part, highestPartId))
+ })
+ case _ => of(messageId, partId, entity, None).map(part => (part, partId))
+ }
+
+ def traverse(messageId: MessageId)(acc: Try[(Option[EmailBodyPart], PartId)], entity: Entity): Try[(Option[EmailBodyPart], PartId)] = {
+ acc.flatMap({
+ case (_, previousPartId) =>
+ val partId = previousPartId.next
+
+ of(messageId, partId, entity)
+ .map({
+ case (part, partId) => (Some(part), partId)
+ })
+ })
+ }
+
+ private def of(messageId: MessageId,
+ partId: PartId,
+ entity: Entity,
+ subParts: Option[List[EmailBodyPart]]): Try[EmailBodyPart] =
+ zip(BlobId.of(messageId, partId), size(entity))
+ .map({
+ case (blobId, size) => EmailBodyPart(
+ partId = partId,
+ blobId = blobId,
+ headers = entity.getHeader.getFields.asScala.toList.map(EmailHeader(_)),
+ size = size,
+ name = Option(entity.getFilename),
+ `type` = entity.getMimeType,
+ charset = Option(entity.getCharset),
+ disposition = Option(entity.getDispositionType),
+ cid = entity.getHeader.getFields("Content-Id").asScala.headOption.map(field => field.getBody),
+ language = entity.getHeader.getFields("Content-Language").asScala.headOption.map(field => field.getBody),
+ location = entity.getHeader.getFields("Content-Location").asScala.headOption.map(field => field.getBody),
+ subParts = subParts)
+ })
+
+ def size(entity: Entity): Try[Size] = {
+ val countingOutputStream: CountingOutputStream = new CountingOutputStream(OutputStream.nullOutputStream())
+ val writer = new DefaultMessageWriter
+ writer.writeEntity(entity, countingOutputStream)
+ refineV[NonNegative](countingOutputStream.getCount) match {
+ case scala.Right(size) => Success(size)
+ case Left(e) => Failure(new IllegalArgumentException(e))
+ }
+ }
+
+ def zip[A, B](a: Try[A], b: Try[B]): Try[(A, B)] = a.flatMap(aValue => b.map(bValue => (aValue, bValue)))
}
case class EmailBodyPart(partId: PartId,
@@ -37,4 +128,6 @@ case class EmailBodyPart(partId: PartId,
cid: Option[String],
language: Option[String],
location: Option[String],
- subParts: Option[List[EmailBodyPart]])
+ subParts: Option[List[EmailBodyPart]]) {
+
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
index 4747e55..f5ea5ae 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailHeader.scala
@@ -19,4 +19,10 @@
package org.apache.james.jmap.mail
+import org.apache.james.mime4j.stream.Field
+
+object EmailHeader {
+ def apply(field: Field): EmailHeader = EmailHeader(field.getName, field.getBody)
+}
+
case class EmailHeader(name: String, value: String)
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org