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


The following commit(s) were added to refs/heads/master by this push:
     new 949491c  JAMES-3609 Allow to inject custom BlobResolver
     new d93a431  Merge pull request #528 from chibenwa/JAMES-3609
949491c is described below

commit 949491c8f4cf93e8450d075af0cd75cced666806
Author: Benoit Tellier <[email protected]>
AuthorDate: Mon Jul 5 15:41:15 2021 +0700

    JAMES-3609 Allow to inject custom BlobResolver
---
 .../james/jmap/rfc8621/RFC8621MethodsModule.java   |  9 ++++
 .../jmap-rfc-8621-integration-tests-common/pom.xml |  4 ++
 .../rfc8621/contract/CustomMethodContract.scala    | 59 ++++++++++++++++++++--
 .../apache/james/jmap/routes/DownloadRoutes.scala  | 27 +++++-----
 4 files changed, 83 insertions(+), 16 deletions(-)

diff --git 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
index c82bdff..bce2e8e 100644
--- 
a/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
+++ 
b/server/container/guice/protocols/jmap/src/main/java/org/apache/james/jmap/rfc8621/RFC8621MethodsModule.java
@@ -64,9 +64,13 @@ import org.apache.james.jmap.method.ThreadGetMethod;
 import org.apache.james.jmap.method.VacationResponseGetMethod;
 import org.apache.james.jmap.method.VacationResponseSetMethod;
 import org.apache.james.jmap.method.ZoneIdProvider;
+import org.apache.james.jmap.routes.AttachmentBlobResolver;
+import org.apache.james.jmap.routes.BlobResolver;
 import org.apache.james.jmap.routes.DownloadRoutes;
 import org.apache.james.jmap.routes.EventSourceRoutes;
 import org.apache.james.jmap.routes.JMAPApiRoutes;
+import org.apache.james.jmap.routes.MessageBlobResolver;
+import org.apache.james.jmap.routes.MessagePartBlobResolver;
 import org.apache.james.jmap.routes.SessionRoutes;
 import org.apache.james.jmap.routes.UploadRoutes;
 import org.apache.james.jmap.routes.WebSocketRoutes;
@@ -135,6 +139,11 @@ public class RFC8621MethodsModule extends AbstractModule {
         
typeNameMultibinder.addBinding().toInstance(EmailSubmissionTypeName$.MODULE$);
         
typeNameMultibinder.addBinding().toInstance(EmailDeliveryTypeName$.MODULE$);
         
typeNameMultibinder.addBinding().toInstance(VacationResponseTypeName$.MODULE$);
+
+        Multibinder<BlobResolver> blobResolverMultibinder = 
Multibinder.newSetBinder(binder(), BlobResolver.class);
+        blobResolverMultibinder.addBinding().to(MessageBlobResolver.class);
+        blobResolverMultibinder.addBinding().to(MessagePartBlobResolver.class);
+        blobResolverMultibinder.addBinding().to(AttachmentBlobResolver.class);
     }
 
     @ProvidesIntoSet
diff --git 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
index 4463737..ae1eeb0 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/pom.xml
@@ -36,6 +36,10 @@
     <dependencies>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>blob-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-server-guice-common</artifactId>
         </dependency>
         <dependency>
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/CustomMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
index b480150..a152b5e 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/CustomMethodContract.scala
@@ -19,12 +19,19 @@
 
 package org.apache.james.jmap.rfc8621.contract
 
+import java.io.{ByteArrayInputStream, InputStream}
+import java.net.URI
+import java.nio.charset.StandardCharsets
+
 import com.google.inject.AbstractModule
 import com.google.inject.multibindings.Multibinder
 import eu.timepit.refined.auto._
+import eu.timepit.refined.numeric.NonNegative
+import eu.timepit.refined.refineV
 import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured._
 import io.restassured.http.ContentType.JSON
+import javax.inject.{Inject, Named}
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
@@ -38,11 +45,17 @@ import 
org.apache.james.jmap.core.ResponseObject.SESSION_STATE
 import org.apache.james.jmap.core.{Capability, CapabilityProperties, State}
 import org.apache.james.jmap.draft.JmapGuiceProbe
 import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.mail
+import org.apache.james.jmap.mail.Email.Size
 import org.apache.james.jmap.method.{InvocationWithContext, Method}
 import org.apache.james.jmap.rfc8621.contract.CustomMethodContract.CUSTOM
+import org.apache.james.jmap.rfc8621.contract.DownloadContract.accountId
 import org.apache.james.jmap.rfc8621.contract.Fixture._
+import org.apache.james.jmap.routes.{Applicable, Blob, BlobResolutionResult, 
BlobResolver, NonApplicable}
 import org.apache.james.mailbox.MailboxSession
+import org.apache.james.mailbox.model.ContentType
 import org.apache.james.utils.{DataProbeImpl, GuiceProbe}
+import org.assertj.core.api.Assertions.assertThat
 import org.junit.jupiter.api.{BeforeEach, Test}
 import org.reactivestreams.Publisher
 import play.api.libs.json.{JsObject, Json}
@@ -57,9 +70,7 @@ import sttp.monad.syntax.MonadErrorOps
 import sttp.ws.WebSocketFrame
 import sttp.ws.WebSocketFrame.Text
 
-import java.net.URI
-import javax.inject.{Inject, Named}
-import scala.util.Try
+import scala.util.{Success, Try}
 
 object CustomMethodContract {
   val CUSTOM: CapabilityIdentifier = "urn:apache:james:params:jmap:custom"
@@ -179,9 +190,33 @@ class CustomMethodModule extends AbstractModule {
     Multibinder.newSetBinder(binder(), classOf[GuiceProbe])
       .addBinding()
       .to(classOf[JmapEventBusProbe])
+    Multibinder.newSetBinder(binder(), classOf[BlobResolver])
+      .addBinding()
+      .to(classOf[CustomBlobResolver])
   }
 }
 
+case object CustomBlob extends Blob {
+  private val payload: Array[Byte] = "zomeuh".getBytes(StandardCharsets.UTF_8)
+
+  override def blobId: mail.BlobId = 
org.apache.james.jmap.mail.BlobId("gabouh")
+
+  override def contentType: ContentType = ContentType.of("application/bytes")
+
+  override def size: Try[Size] = 
Success(refineV[NonNegative](payload.length.toLong).toOption.get)
+
+  override def content: InputStream = new ByteArrayInputStream(payload)
+}
+
+class CustomBlobResolver extends BlobResolver {
+  override def resolve(blobId: org.apache.james.jmap.mail.BlobId, 
mailboxSession: MailboxSession): BlobResolutionResult =
+    if (blobId.equals(CustomBlob.blobId)) {
+      Applicable(SMono.just(CustomBlob))
+    } else {
+      NonApplicable
+    }
+}
+
 class CustomMethod extends Method {
 
   override val methodName = MethodName("Custom/echo")
@@ -463,6 +498,24 @@ trait CustomMethodContract {
          |}""".stripMargin)
   }
 
+  @Test
+  def shouldAcceptCustomBlobResolver(): Unit = {
+    val response = `given`
+      .basePath("")
+      .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+    .when
+      .get(s"/download/$accountId/gabouh")
+    .`then`
+      .statusCode(SC_OK)
+      .contentType("application/bytes")
+      .extract
+      .body
+      .asString
+
+    assertThat(new 
ByteArrayInputStream(response.getBytes(StandardCharsets.UTF_8)))
+      .hasContent("zomeuh")
+  }
+
   private def authenticatedRequest(server: GuiceJamesServer): 
RequestT[Identity, Either[String, String], Any] = {
     val port = server.getProbe(classOf[JmapGuiceProbe])
       .getJmapPort
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
index 866d962..d1a90db 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
@@ -68,7 +68,7 @@ object DownloadRoutes {
 sealed trait BlobResolutionResult {
   def asOption: Option[SMono[Blob]]
 }
-case class NonApplicable() extends BlobResolutionResult {
+case object NonApplicable extends BlobResolutionResult {
   override def asOption: Option[SMono[Blob]] = None
 }
 case class Applicable(blob: SMono[Blob]) extends BlobResolutionResult {
@@ -127,7 +127,7 @@ class MessageBlobResolver @Inject()(val messageIdFactory: 
MessageId.Factory,
                                     val messageIdManager: MessageIdManager) 
extends BlobResolver {
   override def resolve(blobId: BlobId, mailboxSession: MailboxSession): 
BlobResolutionResult = {
     Try(messageIdFactory.fromString(blobId.value.value)) match {
-      case Failure(_) => NonApplicable()
+      case Failure(_) => NonApplicable
       case Success(messageId) => Applicable(SMono.fromPublisher(
         messageIdManager.getMessagesReactive(List(messageId).asJava, 
FetchGroup.FULL_CONTENT, mailboxSession))
         .map[Blob](MessageBlob(blobId, _))
@@ -143,10 +143,9 @@ class AttachmentBlobResolver @Inject()(val 
attachmentManager: AttachmentManager)
         Try(attachmentManager.getAttachment(attachmentId, mailboxSession)) 
match {
           case Success(attachmentMetadata) => Applicable(
             SMono.fromCallable(() => AttachmentBlob(attachmentMetadata, 
attachmentManager.load(attachmentMetadata, mailboxSession))))
-          case Failure(_) => 
Applicable(SMono.error(BlobNotFoundException(blobId)))
+          case Failure(_) => NonApplicable
         }
-
-      case _ => NonApplicable()
+      case _ => NonApplicable
     }
 }
 
@@ -166,7 +165,7 @@ class MessagePartBlobResolver @Inject()(val 
messageIdFactory: MessageId.Factory,
 
   override def resolve(blobId: BlobId, mailboxSession: MailboxSession): 
BlobResolutionResult = {
     asMessageAndPartId(blobId) match {
-      case Failure(_) => NonApplicable()
+      case Failure(_) => NonApplicable
       case Success((messageId, partId)) =>
         Applicable(SMono.fromPublisher(
           messageIdManager.getMessagesReactive(List(messageId).asJava, 
FetchGroup.FULL_CONTENT, mailboxSession))
@@ -186,14 +185,16 @@ class MessagePartBlobResolver @Inject()(val 
messageIdFactory: MessageId.Factory,
   }
 }
 
-class BlobResolvers @Inject()(val messageBlobResolver: MessageBlobResolver,
-                              val messagePartBlobResolver: 
MessagePartBlobResolver,
-                              val attachmentBlobResolver: 
AttachmentBlobResolver) {
+class BlobResolvers(blobResolvers: Set[BlobResolver]) {
+
+  @Inject
+  def this(blobResolvers: java.util.Set[BlobResolver]) {
+    this(blobResolvers.asScala.toSet)
+  }
+
   def resolve(blobId: BlobId, mailboxSession: MailboxSession): SMono[Blob] =
-    messageBlobResolver
-      .resolve(blobId, mailboxSession).asOption
-      .orElse(messagePartBlobResolver.resolve(blobId, mailboxSession).asOption)
-      .orElse(attachmentBlobResolver.resolve(blobId, mailboxSession).asOption)
+    blobResolvers.flatMap(resolver => resolver.resolve(blobId, 
mailboxSession).asOption)
+      .headOption
       .getOrElse(SMono.error(BlobNotFoundException(blobId)))
 }
 

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to