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 2020/04/13 02:53:27 UTC

[james-project] branch master updated (4d0da67 -> 6507a5c)

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 4d0da67  JAMES-2774 Avoid nested block in ReactorRabbitMQChannelPool
     new d6c0a6b  JAMES-3041 Change the way we control resources for Spooler mail processing
     new 9cf4dd6  JAMES-3041 processingActive should be decremented on errors too
     new 5d7b4e4  JAMES-2891 moving common classes into a common module
     new e2357d4  JAMES-2891 JMAP Session Routes
     new 3a53faf  JAMES-2891 AccountId should have its own type
     new d481581  JAMES-2891 Remove JMAPRoutes.logger()
     new 91223ce  JAMES-2891 JMAP Version compliance
     new c6979cc  JAMES-2888: implement Echo method
     new b5679e9  JAMES-2888: Finish work, add testcase, rebase from master
     new d72dba4  JAMES-2888: Remove JMAPRoutes, correct CoreEcho method and test, ordering pom, remove unused [no review] commit. Just unit test, not integration yet.
     new 749c65c  JAMES-2888: add handle IllegalAgrumentsEception and test
     new 2dbba2d  JAMES-2888: fix error when rebase master
     new 6507a5c  JAMES-2888: add BadRequestException removed by rebase work

The 13 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:
 pom.xml                                            |  10 ++
 .../mailetcontainer/impl/JamesMailSpooler.java     |  63 +++-----
 .../mailetcontainer/impl/JamesMailSpoolerTest.java |   5 +-
 .../remote/delivery/RemoteDeliveryTest.java        |   2 +-
 .../SetMessagesOutboxFlagUpdateTest.java           |   4 +-
 .../jmap/draft/exceptions/BadRequestException.java |  18 ---
 .../james/jmap/http/AuthenticationRoutes.java      |  15 +-
 .../org/apache/james/jmap/http/DownloadRoutes.java |  11 +-
 .../org/apache/james/jmap/http/JMAPApiRoutes.java  |  11 +-
 .../org/apache/james/jmap/http/UploadRoutes.java   |   9 +-
 .../apache/james/jmap/http/AuthenticatorTest.java  |   2 +-
 ...meterAccessTokenAuthenticationStrategyTest.java |   2 -
 server/protocols/jmap-rfc-8621/pom.xml             |  41 ++++-
 .../org/apache/james/jmap/http/SessionRoutes.scala |  84 ++++++++++
 .../apache/james/jmap/http/SessionSupplier.scala   |  78 +++++++++
 .../org/apache/james/jmap/json/Serializer.scala    |  22 ++-
 .../RequestObject.scala => method/CoreEcho.scala}  |  19 +--
 .../UnsignedInt.scala => method/Method.scala}      |  16 +-
 .../org/apache/james/jmap/model/Capability.scala   |   4 +-
 .../org/apache/james/jmap/model/Invocation.scala   |   6 +-
 .../apache/james/jmap/model/ResponseObject.scala   |   5 +
 .../org/apache/james/jmap/model/Session.scala      |  52 ++++--
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   | 114 +++++++++++++
 .../apache/james/jmap/http/SessionRoutesTest.scala | 162 +++++++++++++++++++
 .../james/jmap/http/SessionSupplierTest.scala}     |  42 +++--
 .../scala/org/apache/james/jmap/json/Fixture.scala |  13 +-
 .../jmap/json/RequestObjectSerializationTest.scala |  12 +-
 .../json/ResponseObjectSerializationTest.scala     |   8 +-
 .../james/jmap/json/SessionSerializationTest.scala |  30 ++--
 .../apache/james/jmap/method/CoreEchoTest.scala}   |  36 +++--
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 177 +++++++++++++++++++++
 server/protocols/jmap/pom.xml                      |   8 +
 .../java/org/apache/james/jmap/JMAPRoutes.java     |  10 +-
 .../jmap}/exceptions/UnauthorizedException.java    |   2 +-
 .../james/jmap/http/AuthenticationStrategy.java    |   0
 .../org/apache/james/jmap/http/Authenticator.java  |   2 +-
 .../java/org/apache/james/jmap/JMAPServerTest.java |   5 -
 .../routes/MailRepositoriesRoutesTest.java         |   2 +-
 .../webadmin/service/ReprocessingServiceTest.java  |   2 +-
 .../apache/james/queue/api/MailQueueFactory.java   |  47 +++++-
 .../james/queue/api/MailQueueFactoryTest.java      |  26 +++
 .../api/ManageableMailQueueFactoryContract.java    |   6 +-
 .../james/queue/file/FileMailQueueFactory.java     |   4 +-
 .../queue/library/AbstractMailQueueFactory.java    |   6 +-
 .../james/queue/memory/MemoryMailQueueFactory.java |   6 +-
 .../MemoryCacheableMailQueueFactoryTest.java       |   6 +-
 .../org/apache/james/queue/rabbitmq/Dequeuer.java  |  11 +-
 .../queue/rabbitmq/RabbitMQMailQueueFactory.java   |  22 +--
 48 files changed, 993 insertions(+), 245 deletions(-)
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/{model/RequestObject.scala => method/CoreEcho.scala} (71%)
 copy server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/{model/UnsignedInt.scala => method/Method.scala} (74%)
 create mode 100644 server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
 create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
 copy server/{task/task-api/src/main/scala/org/apache/james/task/Hostname.scala => protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala} (51%)
 copy server/{task/task-api/src/main/scala/org/apache/james/task/Hostname.scala => protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala} (51%)
 create mode 100644 server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
 rename server/protocols/{jmap-draft/src/main/java/org/apache/james/jmap/draft => jmap/src/main/java/org/apache/james/jmap}/exceptions/UnauthorizedException.java (96%)
 rename server/protocols/{jmap-draft => jmap}/src/main/java/org/apache/james/jmap/http/AuthenticationStrategy.java (100%)
 rename server/protocols/{jmap-draft => jmap}/src/main/java/org/apache/james/jmap/http/Authenticator.java (97%)
 create mode 100644 server/queue/queue-api/src/test/java/org/apache/james/queue/api/MailQueueFactoryTest.java


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 07/13: JAMES-2891 JMAP Version compliance

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 91223ceb60122e4705746146fb9a1b9fb26becc7
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Fri Apr 10 10:32:04 2020 +0700

    JAMES-2891 JMAP Version compliance
---
 .../org/apache/james/jmap/http/SessionRoutes.scala | 34 ++++++++++++++--------
 .../apache/james/jmap/http/SessionRoutesTest.scala | 16 +++++-----
 2 files changed, 30 insertions(+), 20 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
index 901ef64..f262793 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -19,24 +19,26 @@
 
 package org.apache.james.jmap.http
 
-import java.util.function.BiFunction
+import java.util.stream.Stream
 
 import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
+import io.netty.handler.codec.http.HttpMethod
 import io.netty.handler.codec.http.HttpResponseStatus.OK
 import javax.inject.Inject
 import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8
-import org.apache.james.jmap.JMAPRoutes
+import org.apache.james.jmap.JMAPRoutes.CORS_CONTROL
+import org.apache.james.jmap.JMAPUrls.AUTHENTICATION
 import org.apache.james.jmap.exceptions.UnauthorizedException
 import org.apache.james.jmap.http.SessionRoutes.{JMAP_SESSION, LOGGER}
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.model.Session
-import org.reactivestreams.Publisher
-import org.slf4j.{Logger, LoggerFactory}
+import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
+import org.slf4j.LoggerFactory
 import play.api.libs.json.Json
 import reactor.core.publisher.Mono
 import reactor.core.scala.publisher.SMono
 import reactor.core.scheduler.Schedulers
-import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse, HttpServerRoutes}
+import reactor.netty.http.server.HttpServerResponse
 
 object SessionRoutes {
   private val JMAP_SESSION = "/jmap/session"
@@ -44,23 +46,31 @@ object SessionRoutes {
 }
 
 @Inject
-class SessionRoutes(val authFilter: Authenticator,
+class SessionRoutes(val authenticator: Authenticator,
                     val sessionSupplier: SessionSupplier = new SessionSupplier(),
                     val serializer: Serializer = new Serializer()) extends JMAPRoutes {
 
-  private val generateSession: BiFunction[HttpServerRequest, HttpServerResponse, Publisher[Void]] =
-    (request, response) => SMono.fromPublisher(authFilter.authenticate(request))
+  private val generateSession: JMAPRoute.Action =
+    (request, response) => SMono.fromPublisher(authenticator.authenticate(request))
       .map(_.getUser)
       .flatMap(sessionSupplier.generate)
       .flatMap(session => sendRespond(session, response))
       .onErrorResume(throwable => SMono.fromPublisher(errorHandling(throwable, response)))
       .subscribeOn(Schedulers.elastic())
+      .asJava()
 
-  override def define(builder: HttpServerRoutes): HttpServerRoutes = {
-    builder.get(JMAP_SESSION, generateSession)
-  }
+  override def routes: Stream[JMAPRoute] =
+    Stream.of(
+      JMAPRoute.builder()
+        .endpoint(new Endpoint(HttpMethod.GET, JMAP_SESSION))
+        .action(generateSession)
+        .corsHeaders,
+      JMAPRoute.builder()
+        .endpoint(new Endpoint(HttpMethod.OPTIONS, AUTHENTICATION))
+        .action(CORS_CONTROL)
+        .noCorsHeaders)
 
-  private def sendRespond(session: Session, resp: HttpServerResponse): SMono[Void] =
+  private def sendRespond(session: Session, resp: HttpServerResponse) =
     SMono.fromPublisher(resp.header(CONTENT_TYPE, JSON_CONTENT_TYPE_UTF8)
       .status(OK)
       .sendString(SMono.fromCallable(() => Json.stringify(serializer.serialize(session))))
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index a7b3e9c..df01af4 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -21,6 +21,7 @@ package org.apache.james.jmap.http
 
 import java.nio.charset.StandardCharsets
 
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
 import io.restassured.RestAssured
 import io.restassured.builder.RequestSpecBuilder
 import io.restassured.config.EncoderConfig.encoderConfig
@@ -29,7 +30,7 @@ import io.restassured.http.ContentType
 import org.apache.http.HttpStatus
 import org.apache.james.core.Username
 import org.apache.james.jmap.http.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
-import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutes, JMAPServer}
+import org.apache.james.jmap._
 import org.apache.james.mailbox.MailboxSession
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito._
@@ -50,7 +51,6 @@ object SessionRoutesTest {
 class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
 
   var jmapServer: JMAPServer = _
-  var sessionSupplier: SessionSupplier = _
 
   before {
     val mockedSession = mock(classOf[MailboxSession])
@@ -61,18 +61,18 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
     when(mockedAuthFilter.authenticate(any()))
       .thenReturn(Mono.just(mockedSession))
 
-    sessionSupplier = spy(new SessionSupplier())
-    val jmapRoutes: Set[JMAPRoutes] = Set(new SessionRoutes(
-      sessionSupplier = sessionSupplier,
-      authFilter = mockedAuthFilter))
+    val sessionRoutes = new SessionRoutes(
+      sessionSupplier = new SessionSupplier(),
+      authenticator = mockedAuthFilter)
     jmapServer = new JMAPServer(
       TEST_CONFIGURATION,
-      jmapRoutes.asJava)
+      Set(new JMAPRoutesHandler(Version.RFC8621, sessionRoutes)).asJava,
+      new VersionParser(Set(Version.RFC8621).asJava))
     jmapServer.start()
 
     RestAssured.requestSpecification = new RequestSpecBuilder()
       .setContentType(ContentType.JSON)
-      .setAccept(ContentType.JSON)
+      .addHeader(ACCEPT.toString, s"application/json; jmapVersion=${Version.RFC8621.asString}")
       .setConfig(newConfig.encoderConfig(encoderConfig.defaultContentCharset(StandardCharsets.UTF_8)))
       .setPort(jmapServer.getPort.getValue)
       .setBasePath(SessionRoutesTest.JMAP_SESSION)


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 04/13: JAMES-2891 JMAP Session Routes

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 e2357d4ae94d46bf047a94522058c4d022eb6b3d
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Fri Mar 27 11:50:26 2020 +0700

    JAMES-2891 JMAP Session Routes
---
 server/protocols/jmap-rfc-8621/pom.xml             |  23 +++
 .../org/apache/james/jmap/http/SessionRoutes.scala |  75 ++++++++
 .../apache/james/jmap/http/SessionSupplier.scala   | 108 ++++++++++++
 .../apache/james/jmap/http/SessionRoutesTest.scala | 194 +++++++++++++++++++++
 .../james/jmap/http/SessionSupplierTest.scala      |  57 ++++++
 5 files changed, 457 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index 86f6c0a..79dfe41 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -38,6 +38,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>james-server-jmap</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
@@ -61,6 +65,21 @@
             <version>0.9.13</version>
         </dependency>
         <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
+            <version>0.5.1</version>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.mockito</groupId>
+            <artifactId>mockito-core</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
             <groupId>org.scalatest</groupId>
             <artifactId>scalatest_${scala.base}</artifactId>
             <version>3.1.1</version>
@@ -70,6 +89,10 @@
             <groupId>org.scala-lang.modules</groupId>
             <artifactId>scala-java8-compat_${scala.base}</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>jcl-over-slf4j</artifactId>
+        </dependency>
     </dependencies>
 
     <build>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
new file mode 100644
index 0000000..79bfb45
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -0,0 +1,75 @@
+/** **************************************************************
+ * 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.http
+
+import java.util.function.BiFunction
+
+import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
+import io.netty.handler.codec.http.HttpResponseStatus.OK
+import javax.inject.Inject
+import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8
+import org.apache.james.jmap.JMAPRoutes
+import org.apache.james.jmap.exceptions.UnauthorizedException
+import org.apache.james.jmap.http.SessionRoutes.JMAP_SESSION
+import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.model.Session
+import org.reactivestreams.Publisher
+import org.slf4j.{Logger, LoggerFactory}
+import play.api.libs.json.Json
+import reactor.core.publisher.Mono
+import reactor.core.scala.publisher.SMono
+import reactor.core.scheduler.Schedulers
+import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse, HttpServerRoutes}
+
+object SessionRoutes {
+  private val JMAP_SESSION = "/jmap/session"
+  private val LOGGER = LoggerFactory.getLogger(classOf[SessionRoutes])
+}
+
+@Inject
+class SessionRoutes(val authFilter: Authenticator,
+                    val sessionSupplier: SessionSupplier = new SessionSupplier(),
+                    val serializer: Serializer = new Serializer) extends JMAPRoutes {
+
+  val logger: Logger = SessionRoutes.LOGGER
+  val generateSession: BiFunction[HttpServerRequest, HttpServerResponse, Publisher[Void]] =
+    (request, response) => SMono.fromPublisher(authFilter.authenticate(request))
+      .map(_.getUser)
+      .flatMap(sessionSupplier.generate)
+      .flatMap(session => sendRespond(session, response))
+      .onErrorResume(throwable => SMono.fromPublisher(errorHandling(throwable, response)))
+      .subscribeOn(Schedulers.elastic())
+
+  override def define(builder: HttpServerRoutes): HttpServerRoutes = {
+    builder.get(JMAP_SESSION, generateSession)
+  }
+
+  private def sendRespond(session: Session, resp: HttpServerResponse): SMono[Void] =
+    SMono.fromPublisher(resp.header(CONTENT_TYPE, JSON_CONTENT_TYPE_UTF8)
+      .status(OK)
+      .sendString(SMono.fromCallable(() => Json.stringify(serializer.serialize(session))))
+      .`then`())
+
+  def errorHandling(throwable: Throwable, response: HttpServerResponse): Mono[Void] =
+    throwable match {
+      case _: UnauthorizedException => handleAuthenticationFailure(response, throwable)
+      case _ => handleInternalError(response, throwable)
+    }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
new file mode 100644
index 0000000..c7fb076
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
@@ -0,0 +1,108 @@
+/** **************************************************************
+ * 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.http
+
+import java.net.URL
+
+import com.google.common.annotations.VisibleForTesting
+import eu.timepit.refined.auto._
+import eu.timepit.refined.refineV
+import org.apache.james.core.Username
+import org.apache.james.jmap.http.SessionSupplier.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.jmap.model.Id.Id
+import org.apache.james.jmap.model._
+import reactor.core.publisher.Mono
+import reactor.core.scala.publisher.{SFlux, SMono}
+
+object SessionSupplier {
+  private val CORE_CAPABILITY = CoreCapability(
+    properties = CoreCapabilityProperties(
+      MaxSizeUpload(10_000_000L),
+      MaxConcurrentUpload(4L),
+      MaxSizeRequest(10_000_000L),
+      MaxConcurrentRequests(4L),
+      MaxCallsInRequest(16L),
+      MaxObjectsInGet(500L),
+      MaxObjectsInSet(500L),
+      collationAlgorithms = List("i;unicode-casemap")))
+
+  private val MAIL_CAPABILITY = MailCapability(
+    properties = MailCapabilityProperties(
+      MaxMailboxesPerEmail(Some(10_000_000L)),
+      MaxMailboxDepth(None),
+      MaxSizeMailboxName(200L),
+      MaxSizeAttachmentsPerEmail(20_000_000L),
+      emailQuerySortOptions = List("receivedAt", "cc", "from", "to", "subject", "size", "sentAt", "hasKeyword", "uid", "Id"),
+      MayCreateTopLevelMailbox(true)
+    ))
+}
+
+class SessionSupplier {
+  def generate(username: Username): SMono[Session] =
+    SMono.fromPublisher(
+      Mono.zip(
+        accounts(username).asJava(),
+        primaryAccounts(username).asJava()))
+      .map(tuple => generate(username, tuple.getT1, tuple.getT2))
+
+  private def accounts(username: Username): SMono[Map[Id, Account]] =
+    getId(username)
+      .map(id => Map(
+        id -> Account(
+          username,
+          IsPersonal(true),
+          IsReadOnly(false),
+          accountCapabilities = Set(CORE_CAPABILITY, MAIL_CAPABILITY))))
+
+  private def primaryAccounts(username: Username): SMono[Map[CapabilityIdentifier, Id]] =
+    SFlux.just(CORE_CAPABILITY, MAIL_CAPABILITY)
+      .flatMap(capability => getId(username)
+        .map(id => (capability.identifier, id)))
+      .collectMap(getIdentifier, getId)
+  private def getIdentifier(tuple : (CapabilityIdentifier, Id)): CapabilityIdentifier = tuple._1
+  private def getId(tuple : (CapabilityIdentifier, Id)): Id = tuple._2
+
+  private def getId(username: Username): SMono[Id] = {
+    SMono.fromCallable(() => refineId(username))
+      .flatMap {
+        case Left(errorMessage: String) => SMono.raiseError(new IllegalStateException(errorMessage))
+        case Right(id) => SMono.just(id)
+      }
+  }
+
+  private def refineId(username: Username): Either[String, Id] = refineV(usernameHashCode(username))
+  @VisibleForTesting def usernameHashCode(username: Username) = username.asString().hashCode.toOctalString
+
+  private def generate(username: Username,
+                       accounts: Map[Id, Account],
+                       primaryAccounts: Map[CapabilityIdentifier, Id]): Session = {
+    Session(
+      Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY),
+      accounts,
+      primaryAccounts,
+      username,
+      apiUrl = new URL("http://this-url-is-hardcoded.org/jmap"),
+      downloadUrl = new URL("http://this-url-is-hardcoded.org/download"),
+      uploadUrl = new URL("http://this-url-is-hardcoded.org/upload"),
+      eventSourceUrl = new URL("http://this-url-is-hardcoded.org/eventSource"),
+      state = "000001")
+  }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
new file mode 100644
index 0000000..6ff561d
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -0,0 +1,194 @@
+/** **************************************************************
+ * 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.http
+
+import java.nio.charset.StandardCharsets
+
+import io.restassured.RestAssured
+import io.restassured.builder.RequestSpecBuilder
+import io.restassured.config.EncoderConfig.encoderConfig
+import io.restassured.config.RestAssuredConfig.newConfig
+import io.restassured.http.ContentType
+import org.apache.http.HttpStatus
+import org.apache.james.core.Username
+import org.apache.james.jmap.http.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
+import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutes, JMAPServer}
+import org.apache.james.mailbox.MailboxSession
+import org.hamcrest.CoreMatchers.is
+import org.mockito.ArgumentMatchers.any
+import org.mockito.Mockito._
+import org.scalatest.BeforeAndAfter
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+import play.api.libs.json.Json
+import reactor.core.publisher.Mono
+
+import scala.jdk.CollectionConverters._
+
+object SessionRoutesTest {
+  private val JMAP_SESSION = "/jmap/session"
+  private val TEST_CONFIGURATION = JMAPConfiguration.builder.enable.randomPort.build
+  private val BOB = Username.of("bob@james.org")
+}
+
+class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
+
+  var jmapServer: JMAPServer = _
+  var sessionSupplier: SessionSupplier = _
+
+  before {
+    val mockedSession = mock(classOf[MailboxSession])
+    when(mockedSession.getUser)
+      .thenReturn(BOB)
+
+    val mockedAuthFilter = mock(classOf[Authenticator])
+    when(mockedAuthFilter.authenticate(any()))
+      .thenReturn(Mono.just(mockedSession))
+
+    sessionSupplier = spy(new SessionSupplier())
+    val jmapRoutes: Set[JMAPRoutes] = Set(new SessionRoutes(
+      sessionSupplier = sessionSupplier,
+      authFilter = mockedAuthFilter))
+    jmapServer = new JMAPServer(
+      TEST_CONFIGURATION,
+      jmapRoutes.asJava)
+    jmapServer.start()
+
+    RestAssured.requestSpecification = new RequestSpecBuilder()
+      .setContentType(ContentType.JSON)
+      .setAccept(ContentType.JSON)
+      .setConfig(newConfig.encoderConfig(encoderConfig.defaultContentCharset(StandardCharsets.UTF_8)))
+      .setPort(jmapServer.getPort.getValue)
+      .setBasePath(SessionRoutesTest.JMAP_SESSION)
+      .build()
+  }
+
+  after {
+    jmapServer.stop()
+  }
+
+  "get" should "return OK status" in {
+    RestAssured.when()
+      .get
+    .then
+      .statusCode(HttpStatus.SC_OK)
+      .contentType(ContentType.JSON)
+  }
+
+  "get" should "return correct session" in {
+    val sessionJson = RestAssured.`with`()
+        .get
+      .thenReturn
+        .getBody
+        .asString()
+    val expectedJson = """{
+                         |  "capabilities" : {
+                         |    "urn:ietf:params:jmap:core" : {
+                         |      "maxSizeUpload" : 10000000,
+                         |      "maxConcurrentUpload" : 4,
+                         |      "maxSizeRequest" : 10000000,
+                         |      "maxConcurrentRequests" : 4,
+                         |      "maxCallsInRequest" : 16,
+                         |      "maxObjectsInGet" : 500,
+                         |      "maxObjectsInSet" : 500,
+                         |      "collationAlgorithms" : [ "i;unicode-casemap" ]
+                         |    },
+                         |    "urn:ietf:params:jmap:mail" : {
+                         |      "maxMailboxesPerEmail" : 10000000,
+                         |      "maxMailboxDepth" : null,
+                         |      "maxSizeMailboxName" : 200,
+                         |      "maxSizeAttachmentsPerEmail" : 20000000,
+                         |      "emailQuerySortOptions" : [ "receivedAt", "cc", "from", "to", "subject", "size", "sentAt", "hasKeyword", "uid", "Id" ],
+                         |      "mayCreateTopLevelMailbox" : true
+                         |    }
+                         |  },
+                         |  "accounts" : {
+                         |    "25742733157" : {
+                         |      "name" : "bob@james.org",
+                         |      "isPersonal" : true,
+                         |      "isReadOnly" : false,
+                         |      "accountCapabilities" : {
+                         |        "urn:ietf:params:jmap:core" : {
+                         |          "maxSizeUpload" : 10000000,
+                         |          "maxConcurrentUpload" : 4,
+                         |          "maxSizeRequest" : 10000000,
+                         |          "maxConcurrentRequests" : 4,
+                         |          "maxCallsInRequest" : 16,
+                         |          "maxObjectsInGet" : 500,
+                         |          "maxObjectsInSet" : 500,
+                         |          "collationAlgorithms" : [ "i;unicode-casemap" ]
+                         |        },
+                         |        "urn:ietf:params:jmap:mail" : {
+                         |          "maxMailboxesPerEmail" : 10000000,
+                         |          "maxMailboxDepth" : null,
+                         |          "maxSizeMailboxName" : 200,
+                         |          "maxSizeAttachmentsPerEmail" : 20000000,
+                         |          "emailQuerySortOptions" : [ "receivedAt", "cc", "from", "to", "subject", "size", "sentAt", "hasKeyword", "uid", "Id" ],
+                         |          "mayCreateTopLevelMailbox" : true
+                         |        }
+                         |      }
+                         |    }
+                         |  },
+                         |  "primaryAccounts" : {
+                         |    "urn:ietf:params:jmap:core" : "25742733157",
+                         |    "urn:ietf:params:jmap:mail" : "25742733157"
+                         |  },
+                         |  "username" : "bob@james.org",
+                         |  "apiUrl" : "http://this-url-is-hardcoded.org/jmap",
+                         |  "downloadUrl" : "http://this-url-is-hardcoded.org/download",
+                         |  "uploadUrl" : "http://this-url-is-hardcoded.org/upload",
+                         |  "eventSourceUrl" : "http://this-url-is-hardcoded.org/eventSource",
+                         |  "state" : "000001"
+                         |}""".stripMargin
+
+    Json.parse(sessionJson) should equal(Json.parse(expectedJson))
+  }
+
+  "get" should "return 500 when unexpected Id serialization" in {
+    when(sessionSupplier.usernameHashCode(BOB))
+      .thenReturn("INVALID_JMAP_ID_()*&*$(#*")
+
+    RestAssured.when()
+        .get
+      .then
+        .statusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
+  }
+
+  "get" should "return empty content type when unexpected Id serialization" in {
+    when(sessionSupplier.usernameHashCode(BOB))
+      .thenReturn("INVALID_JMAP_ID_()*&*$(#*")
+
+    RestAssured.when()
+        .get
+      .then
+        .contentType(is(""))
+  }
+
+  "get" should "return empty body when unexpected Id serialization" in {
+    when(sessionSupplier.usernameHashCode(BOB))
+      .thenReturn("INVALID_JMAP_ID_()*&*$(#*")
+
+    RestAssured.`with`()
+        .get
+      .thenReturn()
+        .getBody
+        .asString() should equal("")
+  }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala
new file mode 100644
index 0000000..1b2bb0b
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala
@@ -0,0 +1,57 @@
+/** **************************************************************
+ * 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.http
+
+import eu.timepit.refined.auto._
+import org.apache.james.core.Username
+import org.apache.james.jmap.http.SessionSupplierTest.USERNAME
+import org.apache.james.jmap.model.Id.Id
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
+
+object SessionSupplierTest {
+  private val USERNAME = Username.of("username@james.org")
+}
+
+class SessionSupplierTest extends AnyWordSpec with Matchers {
+
+  "generate" should {
+    "return correct username" in {
+      new SessionSupplier().generate(USERNAME).block().username should equal(USERNAME)
+    }
+
+    "return correct account" which {
+      val accounts = new SessionSupplier().generate(USERNAME).block().accounts
+
+      "has size" in {
+        accounts should have size 1
+      }
+
+      "has name" in {
+        accounts.view.mapValues(_.name).values.toList should equal(List(USERNAME))
+      }
+
+      "has id" in {
+        val usernameHashCode: Id = "22267206120"
+        accounts.keys.toList should equal(List(usernameHashCode))
+      }
+    }
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 13/13: JAMES-2888: add BadRequestException removed by rebase work

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 6507a5c05e9a6f88ad7fa1f8f6b2920efcc2988c
Author: ducnv <du...@gmail.com>
AuthorDate: Fri Apr 10 17:17:50 2020 +0700

    JAMES-2888: add BadRequestException removed by rebase work
---
 .../james/jmap/draft/exceptions/BadRequestException.java     | 12 ++++++++++++
 .../src/main/scala/org/apache/james/jmap/method/Method.scala |  2 +-
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/BadRequestException.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/BadRequestException.java
new file mode 100644
index 0000000..a2c065d
--- /dev/null
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/BadRequestException.java
@@ -0,0 +1,12 @@
+package org.apache.james.jmap.draft.exceptions;
+
+public class BadRequestException extends RuntimeException {
+
+    public BadRequestException(String message) {
+        super(message);
+    }
+
+    public BadRequestException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
index 7a89b84..8ff5b6d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
@@ -1,4 +1,4 @@
-/** **************************************************************
+/****************************************************************
  * 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        *


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 02/13: JAMES-3041 processingActive should be decremented on errors too

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 9cf4dd64430696ec2fd4e46c9b3e6ac36328c0df
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Fri Mar 27 11:37:50 2020 +0100

    JAMES-3041 processingActive should be decremented on errors too
---
 .../apache/james/mailetcontainer/impl/JamesMailSpooler.java  | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
index aaa86d7..9737865 100644
--- a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
+++ b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
@@ -111,14 +111,10 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
 
     private Mono<Void> handleOnQueueItem(MailQueueItem queueItem) {
         TimeMetric timeMetric = metricFactory.timer(SPOOL_PROCESSING);
-        try {
-            return Mono.fromCallable(processingActive::incrementAndGet)
-                .flatMap(ignore -> processMail(queueItem))
-                .doOnSuccess(any -> timeMetric.stopAndPublish().logWhenExceedP99(DEFAULT_100_MS_THRESHOLD))
-                .doOnSuccess(any -> processingActive.decrementAndGet());
-        } catch (Throwable e) {
-            return Mono.error(e);
-        }
+        return Mono.fromCallable(processingActive::incrementAndGet)
+            .flatMap(ignore -> processMail(queueItem))
+            .doOnSuccess(any -> timeMetric.stopAndPublish().logWhenExceedP99(DEFAULT_100_MS_THRESHOLD))
+            .doOnTerminate(processingActive::decrementAndGet);
     }
 
     private Mono<Void> processMail(MailQueueItem queueItem) {


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 05/13: JAMES-2891 AccountId should have its own type

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 3a53fafad2b6d6a7bf7039759f0c13611fa1ca1a
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Mar 31 21:13:18 2020 +0700

    JAMES-2891 AccountId should have its own type
---
 .../org/apache/james/jmap/http/SessionRoutes.scala |  4 +-
 .../apache/james/jmap/http/SessionSupplier.scala   | 74 +++++++---------------
 .../org/apache/james/jmap/json/Serializer.scala    | 17 +++--
 .../org/apache/james/jmap/model/Capability.scala   |  4 +-
 .../org/apache/james/jmap/model/Invocation.scala   |  6 +-
 .../org/apache/james/jmap/model/Session.scala      | 54 ++++++++++++----
 .../apache/james/jmap/http/SessionRoutesTest.scala | 38 +----------
 .../james/jmap/http/SessionSupplierTest.scala      | 10 ++-
 .../james/jmap/json/SessionSerializationTest.scala | 36 ++++++-----
 9 files changed, 111 insertions(+), 132 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
index 79bfb45..75a280b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -46,10 +46,10 @@ object SessionRoutes {
 @Inject
 class SessionRoutes(val authFilter: Authenticator,
                     val sessionSupplier: SessionSupplier = new SessionSupplier(),
-                    val serializer: Serializer = new Serializer) extends JMAPRoutes {
+                    val serializer: Serializer = new Serializer()) extends JMAPRoutes {
 
   val logger: Logger = SessionRoutes.LOGGER
-  val generateSession: BiFunction[HttpServerRequest, HttpServerResponse, Publisher[Void]] =
+  private val generateSession: BiFunction[HttpServerRequest, HttpServerResponse, Publisher[Void]] =
     (request, response) => SMono.fromPublisher(authFilter.authenticate(request))
       .map(_.getUser)
       .flatMap(sessionSupplier.generate)
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
index c7fb076..231617a 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionSupplier.scala
@@ -21,16 +21,12 @@ package org.apache.james.jmap.http
 
 import java.net.URL
 
-import com.google.common.annotations.VisibleForTesting
 import eu.timepit.refined.auto._
-import eu.timepit.refined.refineV
 import org.apache.james.core.Username
-import org.apache.james.jmap.http.SessionSupplier.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.http.SessionSupplier.{CORE_CAPABILITY, HARD_CODED_URL_PREFIX, MAIL_CAPABILITY}
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.Id.Id
 import org.apache.james.jmap.model._
-import reactor.core.publisher.Mono
-import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scala.publisher.SMono
 
 object SessionSupplier {
   private val CORE_CAPABILITY = CoreCapability(
@@ -53,56 +49,30 @@ object SessionSupplier {
       emailQuerySortOptions = List("receivedAt", "cc", "from", "to", "subject", "size", "sentAt", "hasKeyword", "uid", "Id"),
       MayCreateTopLevelMailbox(true)
     ))
+
+  private val HARD_CODED_URL_PREFIX = "http://this-url-is-hardcoded.org"
 }
 
 class SessionSupplier {
-  def generate(username: Username): SMono[Session] =
-    SMono.fromPublisher(
-      Mono.zip(
-        accounts(username).asJava(),
-        primaryAccounts(username).asJava()))
-      .map(tuple => generate(username, tuple.getT1, tuple.getT2))
-
-  private def accounts(username: Username): SMono[Map[Id, Account]] =
-    getId(username)
-      .map(id => Map(
-        id -> Account(
-          username,
-          IsPersonal(true),
-          IsReadOnly(false),
-          accountCapabilities = Set(CORE_CAPABILITY, MAIL_CAPABILITY))))
-
-  private def primaryAccounts(username: Username): SMono[Map[CapabilityIdentifier, Id]] =
-    SFlux.just(CORE_CAPABILITY, MAIL_CAPABILITY)
-      .flatMap(capability => getId(username)
-        .map(id => (capability.identifier, id)))
-      .collectMap(getIdentifier, getId)
-  private def getIdentifier(tuple : (CapabilityIdentifier, Id)): CapabilityIdentifier = tuple._1
-  private def getId(tuple : (CapabilityIdentifier, Id)): Id = tuple._2
-
-  private def getId(username: Username): SMono[Id] = {
-    SMono.fromCallable(() => refineId(username))
-      .flatMap {
-        case Left(errorMessage: String) => SMono.raiseError(new IllegalStateException(errorMessage))
-        case Right(id) => SMono.just(id)
-      }
+  def generate(username: Username): SMono[Session] = {
+    accounts(username)
+      .map(account => Session(
+        Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY),
+        List(account),
+        primaryAccounts(account.accountId),
+        username,
+        apiUrl = new URL(s"$HARD_CODED_URL_PREFIX/jmap"),
+        downloadUrl = new URL(s"$HARD_CODED_URL_PREFIX/download"),
+        uploadUrl = new URL(s"$HARD_CODED_URL_PREFIX/upload"),
+        eventSourceUrl = new URL(s"$HARD_CODED_URL_PREFIX/eventSource")))
   }
 
-  private def refineId(username: Username): Either[String, Id] = refineV(usernameHashCode(username))
-  @VisibleForTesting def usernameHashCode(username: Username) = username.asString().hashCode.toOctalString
+  private def accounts(username: Username): SMono[Account] = SMono.defer(() =>
+    Account.from(username, IsPersonal(true), IsReadOnly(false), Set(CORE_CAPABILITY, MAIL_CAPABILITY)) match {
+      case Left(ex: IllegalArgumentException) => SMono.raiseError(ex)
+      case Right(account: Account) => SMono.just(account)
+    })
 
-  private def generate(username: Username,
-                       accounts: Map[Id, Account],
-                       primaryAccounts: Map[CapabilityIdentifier, Id]): Session = {
-    Session(
-      Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY),
-      accounts,
-      primaryAccounts,
-      username,
-      apiUrl = new URL("http://this-url-is-hardcoded.org/jmap"),
-      downloadUrl = new URL("http://this-url-is-hardcoded.org/download"),
-      uploadUrl = new URL("http://this-url-is-hardcoded.org/upload"),
-      eventSourceUrl = new URL("http://this-url-is-hardcoded.org/eventSource"),
-      state = "000001")
-  }
+  private def primaryAccounts(accountId: AccountId): Map[CapabilityIdentifier, AccountId] =
+    Map(CORE_CAPABILITY.identifier -> accountId, MAIL_CAPABILITY.identifier -> accountId)
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
index ebf9985..2189af9 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
@@ -24,7 +24,6 @@ import java.net.URL
 import org.apache.james.core.Username
 import org.apache.james.jmap.model
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
-import org.apache.james.jmap.model.Id.Id
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
 import org.apache.james.jmap.model.{Account, Invocation, Session, _}
 import play.api.libs.functional.syntax._
@@ -103,17 +102,27 @@ class Serializer {
 
   private implicit val capabilitiesWrites: Writes[Capabilities] = capabilities => setCapabilityWrites.writes(Set(capabilities.coreCapability, capabilities.mailCapability))
 
-  private implicit def identifierMapWrite[Any](implicit idWriter: Writes[Id]): Writes[Map[CapabilityIdentifier, Any]] =
+  private implicit val accountIdWrites: Format[AccountId] = Json.valueFormat[AccountId]
+  private implicit def identifierMapWrite[Any](implicit idWriter: Writes[AccountId]): Writes[Map[CapabilityIdentifier, Any]] =
     (m: Map[CapabilityIdentifier, Any]) => {
       m.foldLeft(JsObject.empty)((jsObject, kv) => {
-        val (identifier: CapabilityIdentifier, id: Id) = kv
+        val (identifier: CapabilityIdentifier, id: AccountId) = kv
         jsObject.+(identifier.value, idWriter.writes(id))
       })
     }
 
   private implicit val isPersonalFormat: Format[IsPersonal] = Json.valueFormat[IsPersonal]
   private implicit val isReadOnlyFormat: Format[IsReadOnly] = Json.valueFormat[IsReadOnly]
-  private implicit val accountWrites: Writes[Account] = Json.writes[Account]
+  private implicit val accountWrites: Writes[Account] = (
+      (JsPath \ Account.NAME).write[Username] and
+      (JsPath \ Account.IS_PERSONAL).write[IsPersonal] and
+      (JsPath \ Account.IS_READ_ONLY).write[IsReadOnly] and
+      (JsPath \ Account.ACCOUNT_CAPABILITIES).write[Set[_ <: Capability]]
+    ) (unlift(Account.unapplyIgnoreAccountId))
+
+  private implicit def accountListWrites(implicit accountWrites: Writes[Account]): Writes[List[Account]] =
+    (list: List[Account]) => JsObject(list.map(account => (account.accountId.id.value, accountWrites.writes(account))))
+
   private implicit val sessionWrites: Writes[Session] = Json.writes[Session]
 
   def serialize(session: Session): JsValue = {
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
index 8e35c7d..17517fe 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Capability.scala
@@ -30,8 +30,8 @@ import org.apache.james.jmap.model.UnsignedInt.UnsignedInt
 
 object CapabilityIdentifier {
   type CapabilityIdentifier = String Refined Uri
-  val JMAP_CORE: CapabilityIdentifier = "urn:ietf:params:jmap:core"
-  val JMAP_MAIL: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
+  private[model] val JMAP_CORE: CapabilityIdentifier = "urn:ietf:params:jmap:core"
+  private[model] val JMAP_MAIL: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
 }
 
 sealed trait CapabilityProperties
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala
index 7ad8e5c..c46d95b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Invocation.scala
@@ -25,9 +25,9 @@ import play.api.libs.json._
 case class Invocation(methodName: MethodName, arguments: Arguments, methodCallId: MethodCallId)
 
 object Invocation {
-  val METHOD_NAME: Int = 0
-  val ARGUMENTS: Int = 1
-  val METHOD_CALL: Int = 2
+  private[jmap] val METHOD_NAME: Int = 0
+  private[jmap] val ARGUMENTS: Int = 1
+  private[jmap] val METHOD_CALL: Int = 2
 
   case class MethodName(value: NonEmptyString)
   case class Arguments(value: JsObject) extends AnyVal
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala
index 5d85fca..0bb733d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala
@@ -20,44 +20,76 @@
 package org.apache.james.jmap.model
 
 import java.net.URL
+import java.nio.charset.StandardCharsets
 
+import com.google.common.hash.Hashing
 import eu.timepit.refined.api.Refined
+import eu.timepit.refined.auto._
 import eu.timepit.refined.collection.NonEmpty
+import eu.timepit.refined.refineV
 import org.apache.james.core.Username
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Id.Id
-import org.apache.james.jmap.model.State.State
+import org.apache.james.jmap.model.State.{INSTANCE, State}
 
 case class IsPersonal(value: Boolean)
 case class IsReadOnly(value: Boolean)
 
-object Account {
-  def apply(name: Username,
-            isPersonal: IsPersonal,
-            isReadOnly: IsReadOnly,
-            accountCapabilities: Set[_ <: Capability]): Account = {
+object AccountId {
+  def from(username: Username): Either[IllegalArgumentException, AccountId] = {
+    val sha256String = Hashing.sha256()
+      .hashString(username.asString(), StandardCharsets.UTF_8)
+      .toString
+    val refinedId: Either[String, Id] = refineV(sha256String)
 
-    new Account(name, isPersonal, isReadOnly, accountCapabilities)
+    refinedId match {
+      case Left(errorMessage: String) => Left(new IllegalArgumentException(errorMessage))
+      case Right(id) => Right(AccountId(id))
+    }
   }
 }
 
-final case class Account private(name: Username,
+final case class AccountId(id: Id)
+
+object Account {
+  private[jmap] val NAME = "name";
+  private[jmap] val IS_PERSONAL = "isPersonal"
+  private[jmap] val IS_READ_ONLY = "isReadOnly"
+  private[jmap] val ACCOUNT_CAPABILITIES = "accountCapabilities"
+
+  def from(name: Username,
+           isPersonal: IsPersonal,
+           isReadOnly: IsReadOnly,
+           accountCapabilities: Set[_ <: Capability]): Either[IllegalArgumentException, Account] =
+    AccountId.from(name) match {
+      case Left(ex: IllegalArgumentException) => Left(ex)
+      case Right(accountId) => Right(new Account(accountId, name, isPersonal, isReadOnly, accountCapabilities))
+    }
+
+  def unapplyIgnoreAccountId(account: Account): Some[(Username, IsPersonal, IsReadOnly, Set[_ <: Capability])] =
+    Some(account.name, account.isPersonal, account.isReadOnly, account.accountCapabilities)
+}
+
+final case class Account private(accountId: AccountId,
+                                 name: Username,
                                  isPersonal: IsPersonal,
                                  isReadOnly: IsReadOnly,
                                  accountCapabilities: Set[_ <: Capability])
 
 object State {
+  private[model] val INSTANCE: State = "000001"
+
   type State = String Refined NonEmpty
 }
 
 case class Capabilities(coreCapability: CoreCapability, mailCapability: MailCapability)
 
 final case class Session(capabilities: Capabilities,
-                         accounts: Map[Id, Account],
-                         primaryAccounts: Map[CapabilityIdentifier, Id],
+                         accounts: List[Account],
+                         primaryAccounts: Map[CapabilityIdentifier, AccountId],
                          username: Username,
                          apiUrl: URL,
                          downloadUrl: URL,
                          uploadUrl: URL,
                          eventSourceUrl: URL,
-                         state: State)
+                         state: State = INSTANCE)
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
index 6ff561d..a7b3e9c 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionRoutesTest.scala
@@ -31,7 +31,6 @@ import org.apache.james.core.Username
 import org.apache.james.jmap.http.SessionRoutesTest.{BOB, TEST_CONFIGURATION}
 import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutes, JMAPServer}
 import org.apache.james.mailbox.MailboxSession
-import org.hamcrest.CoreMatchers.is
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mockito._
 import org.scalatest.BeforeAndAfter
@@ -120,7 +119,7 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |    }
                          |  },
                          |  "accounts" : {
-                         |    "25742733157" : {
+                         |    "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401" : {
                          |      "name" : "bob@james.org",
                          |      "isPersonal" : true,
                          |      "isReadOnly" : false,
@@ -147,8 +146,8 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
                          |    }
                          |  },
                          |  "primaryAccounts" : {
-                         |    "urn:ietf:params:jmap:core" : "25742733157",
-                         |    "urn:ietf:params:jmap:mail" : "25742733157"
+                         |    "urn:ietf:params:jmap:core" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401",
+                         |    "urn:ietf:params:jmap:mail" : "0fe275bf13ff761407c17f64b1dfae2f4b3186feea223d7267b79f873a105401"
                          |  },
                          |  "username" : "bob@james.org",
                          |  "apiUrl" : "http://this-url-is-hardcoded.org/jmap",
@@ -160,35 +159,4 @@ class SessionRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
 
     Json.parse(sessionJson) should equal(Json.parse(expectedJson))
   }
-
-  "get" should "return 500 when unexpected Id serialization" in {
-    when(sessionSupplier.usernameHashCode(BOB))
-      .thenReturn("INVALID_JMAP_ID_()*&*$(#*")
-
-    RestAssured.when()
-        .get
-      .then
-        .statusCode(HttpStatus.SC_INTERNAL_SERVER_ERROR)
-  }
-
-  "get" should "return empty content type when unexpected Id serialization" in {
-    when(sessionSupplier.usernameHashCode(BOB))
-      .thenReturn("INVALID_JMAP_ID_()*&*$(#*")
-
-    RestAssured.when()
-        .get
-      .then
-        .contentType(is(""))
-  }
-
-  "get" should "return empty body when unexpected Id serialization" in {
-    when(sessionSupplier.usernameHashCode(BOB))
-      .thenReturn("INVALID_JMAP_ID_()*&*$(#*")
-
-    RestAssured.`with`()
-        .get
-      .thenReturn()
-        .getBody
-        .asString() should equal("")
-  }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala
index 1b2bb0b..11bd415 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/http/SessionSupplierTest.scala
@@ -19,10 +19,8 @@
 
 package org.apache.james.jmap.http
 
-import eu.timepit.refined.auto._
 import org.apache.james.core.Username
 import org.apache.james.jmap.http.SessionSupplierTest.USERNAME
-import org.apache.james.jmap.model.Id.Id
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
 
@@ -45,12 +43,12 @@ class SessionSupplierTest extends AnyWordSpec with Matchers {
       }
 
       "has name" in {
-        accounts.view.mapValues(_.name).values.toList should equal(List(USERNAME))
+        accounts.map(_.name) should equal(List(USERNAME))
       }
 
-      "has id" in {
-        val usernameHashCode: Id = "22267206120"
-        accounts.keys.toList should equal(List(usernameHashCode))
+      "has accountId being hash of username in string" in {
+        accounts.map(_.accountId)
+          .map(_.id.value) should equal(List("0cb33e029628ea603d1b988f0f81b069d89b6c5a093e12b275ecdc626bd7458c"))
       }
     }
   }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
index 8f6f228..98e3f81 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
@@ -50,9 +50,7 @@ object SessionSerializationTest {
   private val MAX_OBJECTS_IN_SET : MaxObjectsInSet = MaxObjectsInSet(128L)
   private val COLLATION_ALGORITHMS : List[CollationAlgorithm] = List(ALGO_1, ALGO_2, ALGO_3)
   private val USER_1 = Username.of("user1@james.org")
-  private val USER_1_ID: Id = "user1Id"
   private val USER_2 = Username.of("user2@james.org")
-  private val USER_2_ID: Id = "user2Id"
   private val URL = new URL("http://james.org")
   private val STATE : State = "fda9342jcm"
 
@@ -87,28 +85,32 @@ object SessionSerializationTest {
   private val IS_NOT_PERSONAL : IsPersonal = IsPersonal(false)
   private val IS_NOT_READ_ONLY : IsReadOnly = IsReadOnly(false)
 
-  private val ACCOUNT_1 = Account(
+  private val ACCOUNT_1: Account = Account.from(
     name = USER_1,
     isPersonal = IS_PERSONAL,
     isReadOnly = IS_NOT_READ_ONLY,
-    accountCapabilities = Set(CORE_CAPABILITY))
-  private val ACCOUNT_2 = Account(
+    accountCapabilities = Set(CORE_CAPABILITY)) match {
+      case Left(ex: IllegalArgumentException) => throw ex
+      case Right(account: Account) => account
+    }
+
+  private val ACCOUNT_2: Account = Account.from(
     name = USER_2,
     isPersonal = IS_NOT_PERSONAL,
     isReadOnly = IS_NOT_READ_ONLY,
-    accountCapabilities = Set(CORE_CAPABILITY))
-  private val ACCOUNTS = Map(
-    USER_1_ID -> ACCOUNT_1,
-    USER_2_ID -> ACCOUNT_2,
-  )
+    accountCapabilities = Set(CORE_CAPABILITY))  match {
+      case Left(ex: IllegalArgumentException) => throw ex
+      case Right(account: Account) => account
+    }
+
   private val PRIMARY_ACCOUNTS = Map(
-    MAIL_IDENTIFIER -> USER_1_ID,
-    CONTACT_IDENTIFIER -> USER_2_ID
+    MAIL_IDENTIFIER -> ACCOUNT_1.accountId,
+    CONTACT_IDENTIFIER -> ACCOUNT_1.accountId
   )
 
   private val SESSION = Session(
     capabilities = CAPABILITIES,
-    accounts = ACCOUNTS,
+    accounts = List(ACCOUNT_1, ACCOUNT_2),
     primaryAccounts = PRIMARY_ACCOUNTS,
     username = USER_1,
     apiUrl = URL,
@@ -155,7 +157,7 @@ class SessionSerializationTest extends AnyWordSpec with Matchers {
           |    }
           |  },
           |  "accounts": {
-          |    "user1Id": {
+          |    "807a5306ccb4527af7790a0f9b48a776514bdbfba064e355461a76bcffbf2c90": {
           |      "name": "user1@james.org",
           |      "isPersonal": true,
           |      "isReadOnly": false,
@@ -176,7 +178,7 @@ class SessionSerializationTest extends AnyWordSpec with Matchers {
           |        }
           |      }
           |    },
-          |    "user2Id": {
+          |    "a9b46834e106ff73268a40a34ffba9fcfeee8bdb601939d1a96ef9199dc2695c": {
           |      "name": "user2@james.org",
           |      "isPersonal": false,
           |      "isReadOnly": false,
@@ -199,8 +201,8 @@ class SessionSerializationTest extends AnyWordSpec with Matchers {
           |    }
           |  },
           |  "primaryAccounts": {
-          |    "urn:ietf:params:jmap:mail": "user1Id",
-          |    "urn:ietf:params:jmap:contact": "user2Id"
+          |    "urn:ietf:params:jmap:mail": "807a5306ccb4527af7790a0f9b48a776514bdbfba064e355461a76bcffbf2c90",
+          |    "urn:ietf:params:jmap:contact": "807a5306ccb4527af7790a0f9b48a776514bdbfba064e355461a76bcffbf2c90"
           |  },
           |  "username": "user1@james.org",
           |  "apiUrl": "http://james.org",


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 10/13: JAMES-2888: Remove JMAPRoutes, correct CoreEcho method and test, ordering pom, remove unused [no review] commit. Just unit test, not integration yet.

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 d72dba483f54fd540fcd1a722d62b982229e8c9c
Author: ducnv <du...@gmail.com>
AuthorDate: Wed Apr 1 08:59:18 2020 +0700

    JAMES-2888: Remove JMAPRoutes, correct CoreEcho method and test, ordering pom, remove unused [no review] commit.
    Just unit test, not integration yet.
---
 pom.xml                                            |   5 +
 server/protocols/jmap-rfc-8621/pom.xml             |  42 +++---
 .../org/apache/james/jmap/json/Serializer.scala    |   5 +
 .../ResponseObject.scala => method/CoreEcho.scala} |  14 +-
 .../ResponseObject.scala => method/Method.scala}   |  13 +-
 .../apache/james/jmap/model/ResponseObject.scala   |   5 +
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   |  99 +++++++++-----
 .../scala/org/apache/james/jmap/json/Fixture.scala |  13 +-
 .../jmap/json/RequestObjectSerializationTest.scala |  12 +-
 .../json/ResponseObjectSerializationTest.scala     |   8 +-
 .../apache/james/jmap/method/CoreEchoTest.scala    |   3 +-
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 152 +++++++++++++++++++++
 12 files changed, 299 insertions(+), 72 deletions(-)

diff --git a/pom.xml b/pom.xml
index a30cb9b..d39bd79 100644
--- a/pom.xml
+++ b/pom.xml
@@ -664,6 +664,11 @@
                 <scope>import</scope>
             </dependency>
             <dependency>
+                <groupId>io.projectreactor</groupId>
+                <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
+                <version>0.5.1</version>
+            </dependency>
+            <dependency>
                 <groupId>${james.groupId}</groupId>
                 <artifactId>apache-james-backends-cassandra</artifactId>
                 <version>${project.version}</version>
diff --git a/server/protocols/jmap-rfc-8621/pom.xml b/server/protocols/jmap-rfc-8621/pom.xml
index 79dfe41..bb6a83b 100644
--- a/server/protocols/jmap-rfc-8621/pom.xml
+++ b/server/protocols/jmap-rfc-8621/pom.xml
@@ -33,6 +33,28 @@
 
     <dependencies>
         <dependency>
+            <groupId>com.typesafe.play</groupId>
+            <artifactId>play-json_${scala.base}</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>eu.timepit</groupId>
+            <artifactId>refined_${scala.base}</artifactId>
+            <version>0.9.13</version>
+        </dependency>
+        <dependency>
+            <groupId>io.rest-assured</groupId>
+            <artifactId>rest-assured</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor.netty</groupId>
+            <artifactId>reactor-netty</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.projectreactor</groupId>
+            <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
+        </dependency>
+        <dependency>
             <groupId>${james.groupId}</groupId>
             <artifactId>james-core</artifactId>
         </dependency>
@@ -56,25 +78,6 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>com.typesafe.play</groupId>
-            <artifactId>play-json_${scala.base}</artifactId>
-        </dependency>
-        <dependency>
-            <groupId>eu.timepit</groupId>
-            <artifactId>refined_${scala.base}</artifactId>
-            <version>0.9.13</version>
-        </dependency>
-        <dependency>
-            <groupId>io.projectreactor</groupId>
-            <artifactId>reactor-scala-extensions_${scala.base}</artifactId>
-            <version>0.5.1</version>
-        </dependency>
-        <dependency>
-            <groupId>io.rest-assured</groupId>
-            <artifactId>rest-assured</artifactId>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
             <groupId>org.mockito</groupId>
             <artifactId>mockito-core</artifactId>
             <scope>test</scope>
@@ -94,7 +97,6 @@
             <artifactId>jcl-over-slf4j</artifactId>
         </dependency>
     </dependencies>
-
     <build>
         <plugins>
             <plugin>
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
index 2189af9..173a82d 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/Serializer.scala
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.json
 
+import java.io.InputStream
 import java.net.URL
 
 import org.apache.james.core.Username
@@ -141,6 +142,10 @@ class Serializer {
     Json.parse(input).validate[RequestObject]
   }
 
+  def deserializeRequestObject(input: InputStream): JsResult[RequestObject] = {
+    Json.parse(input).validate[RequestObject]
+  }
+
   def deserializeResponseObject(input: String): JsResult[ResponseObject] = {
     Json.parse(input).validate[ResponseObject]
   }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEcho.scala
similarity index 71%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEcho.scala
index a50d3a2..efeb0e7 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/CoreEcho.scala
@@ -16,9 +16,17 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  * ***************************************************************/
+package org.apache.james.jmap.method
 
-package org.apache.james.jmap.model
 
-import org.apache.james.jmap.model.State.State
+import eu.timepit.refined.auto._
+import org.apache.james.jmap.model.Invocation
+import org.apache.james.jmap.model.Invocation.MethodName
+import org.reactivestreams.Publisher
+import reactor.core.scala.publisher.SMono
 
-case class ResponseObject(sessionState: State, methodResponses: Seq[Invocation])
+class CoreEcho extends Method {
+  override val methodName = MethodName("Core/echo")
+
+  override def process(invocation: Invocation): Publisher[Invocation] = SMono.just(invocation)
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
similarity index 78%
copy from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
copy to server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
index a50d3a2..7a89b84 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/Method.scala
@@ -16,9 +16,16 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  * ***************************************************************/
+package org.apache.james.jmap.method
 
-package org.apache.james.jmap.model
 
-import org.apache.james.jmap.model.State.State
+import org.apache.james.jmap.model.Invocation
+import org.apache.james.jmap.model.Invocation.MethodName
+import org.reactivestreams.Publisher
+
+trait Method {
+  val methodName: MethodName
+
+  def process(invocation: Invocation): Publisher[Invocation]
+}
 
-case class ResponseObject(sessionState: State, methodResponses: Seq[Invocation])
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
index a50d3a2..783801b 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/ResponseObject.scala
@@ -19,6 +19,11 @@
 
 package org.apache.james.jmap.model
 
+import eu.timepit.refined.auto._
 import org.apache.james.jmap.model.State.State
 
 case class ResponseObject(sessionState: State, methodResponses: Seq[Invocation])
+
+object ResponseObject {
+  val SESSION_STATE: State = "75128aab4b1b"
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index ddfe2fe..8336487 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -18,52 +18,87 @@
  * ***************************************************************/
 package org.apache.james.jmap.routes
 
+import java.io.InputStream
+import java.nio.charset.StandardCharsets
+import java.util.stream
+import java.util.stream.Stream
+
 import eu.timepit.refined.auto._
+import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
+import io.netty.handler.codec.http.HttpMethod
+import io.netty.handler.codec.http.HttpResponseStatus.OK
+import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
+import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.method.CoreEcho
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
 import org.apache.james.jmap.model.{Invocation, RequestObject, ResponseObject}
-import org.reactivestreams.Publisher
+import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
+import org.slf4j.{Logger, LoggerFactory}
 import play.api.libs.json.{JsError, JsSuccess, Json}
-import reactor.core.scala.publisher.SMono
+import reactor.core.publisher.Mono
+import reactor.core.scala.publisher.{SFlux, SMono}
+import reactor.core.scheduler.Schedulers
 import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
 
-object JMAPApiRoutes {
-  private val ECHO_METHOD = new CoreEcho()
-}
+class JMAPApiRoutes extends JMAPRoutes {
+  override def logger(): Logger = LoggerFactory.getLogger(getClass)
+
+  private val coreEcho = new CoreEcho
 
-class JMAPApiRoutes {
-  private val echoMethod = JMAPApiRoutes.ECHO_METHOD
+  override def routes(): stream.Stream[JMAPRoute] = Stream.of(
+    JMAPRoute.builder
+      .endpoint(new Endpoint(HttpMethod.POST, JMAP))
+      .action(this.post)
+      .corsHeaders,
+    JMAPRoute.builder
+      .endpoint(new Endpoint(HttpMethod.OPTIONS, JMAP))
+      .action(JMAPRoutes.CORS_CONTROL)
+      .corsHeaders())
 
-  def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): SMono[Void] = {
-    SMono.fromPublisher(extractRequestObject(httpServerRequest))
-      .flatMap(this.process)
-      .doOnError(e => new RuntimeException(e.getMessage))
+  private def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): Mono[Void] =
+    this.requestAsJsonStream(httpServerRequest)
+      .flatMap(requestObject => this.process(requestObject, httpServerResponse))
+      .onErrorResume(throwable => SMono.fromPublisher(handleInternalError(httpServerResponse, throwable)))
+      .subscribeOn(Schedulers.elastic)
+      .asJava()
       .`then`()
+
+  private def requestAsJsonStream(httpServerRequest: HttpServerRequest): SMono[RequestObject] = {
+    SMono.fromPublisher(httpServerRequest
+      .receive()
+      .aggregate()
+      .asInputStream())
+      .flatMap(this.parseRequestObject)
   }
 
-  private def process(requestObject: RequestObject): SMono[ResponseObject] = {
-    SMono.just(
-      requestObject.methodCalls.map((invocation: Invocation) =>
-        invocation.methodName match {
-          case echoMethod.methodName => echoMethod.process(invocation)
-          case _ => SMono.just(new Invocation(
-            MethodName("error"),
-            Arguments(Json.obj("type" -> "Not implemented")),
-            invocation.methodCallId))
-        }
+  private def parseRequestObject(inputStream: InputStream): SMono[RequestObject] =
+    new Serializer().deserializeRequestObject(inputStream) match {
+      case JsSuccess(requestObject, _) => SMono.just(requestObject)
+      case JsError(errors) => SMono.raiseError(new RuntimeException(errors.toString()))
+    }
+
+  private def process(requestObject: RequestObject, httpServerResponse: HttpServerResponse): SMono[Void] =
+    requestObject
+      .methodCalls
+      .map(this.processMethodWithMatchName)
+      .foldLeft(SFlux.empty[Invocation]) { (flux: SFlux[Invocation], mono: SMono[Invocation]) => flux.mergeWith(mono) }
+      .collectSeq()
+      .flatMap((invocations: Seq[Invocation]) =>
+        SMono.fromPublisher(httpServerResponse.status(OK)
+          .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
+          .sendString(
+            SMono.fromCallable(() =>
+              new Serializer().serialize(ResponseObject(ResponseObject.SESSION_STATE, invocations)).toString()),
+            StandardCharsets.UTF_8
+          ).`then`())
       )
-    ).flatMap((invocations: Seq[Invocation]) => SMono.just(ResponseObject(ResponseObject.SESSION_STATE, invocations)))
-  }
 
-  private def extractRequestObject(httpServerRequest: HttpServerRequest): Publisher[RequestObject] = {
-    httpServerRequest
-      .receive()
-      .asInputStream()
-      .flatMap(inputStream => new Serializer().deserializeRequestObject(inputStream) match {
-        case JsSuccess(requestObject, _) => SMono.just(new ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls))
-        case JsError(errors) => SMono.raiseError(new RuntimeException(errors.toString()))
-      })
+  private def processMethodWithMatchName(invocation: Invocation): SMono[Invocation] = invocation.methodName match {
+    case coreEcho.methodName => SMono.fromPublisher(coreEcho.process(invocation))
+    case _ => SMono.just(new Invocation(
+      MethodName("error"),
+      Arguments(Json.obj("type" -> "Not implemented")),
+      invocation.methodCallId))
   }
 }
-
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
index bba26fa..e249535 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/Fixture.scala
@@ -23,7 +23,7 @@ import eu.timepit.refined.auto._
 import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
 import org.apache.james.jmap.model.Id.Id
 import org.apache.james.jmap.model.Invocation.{Arguments, MethodCallId, MethodName}
-import org.apache.james.jmap.model.{ClientId, CreatedIds, Invocation, ServerId}
+import org.apache.james.jmap.model.{ClientId, CreatedIds, Invocation, ResponseObject, ServerId}
 import play.api.libs.json.Json
 
 object Fixture {
@@ -32,12 +32,19 @@ object Fixture {
   val coreIdentifier: CapabilityIdentifier = "urn:ietf:params:jmap:core"
   val mailIdentifier: CapabilityIdentifier = "urn:ietf:params:jmap:mail"
   val invocation1: Invocation = Invocation(
-    methodName = MethodName("Core/echo1"),
+    methodName = MethodName("Core/echo"),
     arguments = Arguments(Json.obj("arg1" -> "arg1data", "arg2" -> "arg2data")),
     methodCallId = MethodCallId("c1"))
   val invocation2: Invocation = Invocation(
-    methodName = MethodName("Core/echo2"),
+    methodName = MethodName("Core/echo"),
     arguments = Arguments(Json.obj("arg3" -> "arg3data", "arg4" -> "arg4data")),
     methodCallId = MethodCallId("c2")
   )
+  val unsupportedInvocation: Invocation = Invocation(
+    methodName = MethodName("error"),
+    arguments = Arguments(Json.obj("type" -> "Not implemented")),
+    methodCallId = MethodCallId("notsupport"))
+  val responseObject1: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation1))
+  val responseObject2: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation2))
+  val responseObjectWithUnsupportedMethod: ResponseObject = ResponseObject(ResponseObject.SESSION_STATE, Seq(invocation1, unsupportedInvocation))
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
index 173c026..4d1c5bd 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/RequestObjectSerializationTest.scala
@@ -39,7 +39,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
             |{
             |  "using": [ "urn:ietf:params:jmap:core"],
             |  "methodCalls": [
-            |    [ "Core/echo1", {
+            |    [ "Core/echo", {
             |      "arg1": "arg1data",
             |      "arg2": "arg2data"
             |    }, "c1" ]
@@ -60,7 +60,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
             |{
             |  "using": [ "urn:ietf:params:jmap:core"],
             |  "methodCalls": [
-            |    [ "Core/echo1", {
+            |    [ "Core/echo", {
             |      "arg1": "arg1data",
             |      "arg2": "arg2data"
             |    }, "c1" ]
@@ -82,11 +82,11 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
             |{
             |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
             |  "methodCalls": [
-            |    [ "Core/echo1", {
+            |    [ "Core/echo", {
             |      "arg1": "arg1data",
             |      "arg2": "arg2data"
             |    }, "c1" ],
-            |    [ "Core/echo2", {
+            |    [ "Core/echo", {
             |      "arg3": "arg3data",
             |      "arg4": "arg4data"
             |    }, "c2" ]
@@ -109,7 +109,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
           |  "methodCalls": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
@@ -132,7 +132,7 @@ class RequestObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "using": [ "urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
           |  "methodCalls": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
index 6dbbd8e..11c2504 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/ResponseObjectSerializationTest.scala
@@ -37,7 +37,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
         """
           |{
           |  "methodResponses": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
@@ -57,11 +57,11 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "sessionState": "75128aab4b1b",
           |  "methodResponses": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ],
-          |    [ "Core/echo2", {
+          |    [ "Core/echo", {
           |      "arg3": "arg3data",
           |      "arg4": "arg4data"
           |    }, "c2" ]
@@ -82,7 +82,7 @@ class ResponseObjectSerializationTest extends AnyWordSpec with Matchers {
           |{
           |  "sessionState": "75128aab4b1b",
           |  "methodResponses": [
-          |    [ "Core/echo1", {
+          |    [ "Core/echo", {
           |      "arg1": "arg1data",
           |      "arg2": "arg2data"
           |    }, "c1" ]
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
index b723321..2d78e5f 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
@@ -22,6 +22,7 @@ import org.apache.james.jmap.json.Fixture.{invocation1, invocation2}
 import org.apache.james.jmap.model.Invocation
 import org.scalatest.matchers.should.Matchers
 import org.scalatest.wordspec.AnyWordSpec
+import reactor.core.scala.publisher.SMono
 
 class CoreEchoTest extends AnyWordSpec with Matchers {
   private val echoMethod: CoreEcho = new CoreEcho()
@@ -38,7 +39,7 @@ class CoreEchoTest extends AnyWordSpec with Matchers {
       "success and not return anything else different than the original invocation" in {
         val wrongExpected: Invocation = invocation2
         val dataResponse = SMono.fromPublisher(echoMethod.process(invocation1)).block()
-
+        
         dataResponse should not be(wrongExpected)
       }
     }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
new file mode 100644
index 0000000..55bf125
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -0,0 +1,152 @@
+/** **************************************************************
+ * 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.routes
+
+import java.nio.charset.StandardCharsets
+
+import com.google.common.collect.ImmutableSet
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured
+import io.restassured.builder.RequestSpecBuilder
+import io.restassured.config.EncoderConfig.encoderConfig
+import io.restassured.config.RestAssuredConfig.newConfig
+import io.restassured.http.ContentType
+import org.apache.http.HttpStatus
+import org.apache.james.jmap.JMAPUrls.JMAP
+import org.apache.james.jmap.json.Fixture._
+import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.model.RequestObject
+import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutesHandler, JMAPServer, Version}
+import org.scalatest.BeforeAndAfter
+import org.scalatest.flatspec.AnyFlatSpec
+import org.scalatest.matchers.should.Matchers
+
+class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
+
+  private val TEST_CONFIGURATION: JMAPConfiguration = JMAPConfiguration.builder().enable().randomPort().build()
+  private val ACCEPT_JMAP_VERSION_HEADER = "application/json; jmapVersion="
+  private val ACCEPT_DRAFT_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.DRAFT.getVersion
+  private val ACCEPT_RFC8621_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.RFC8621.getVersion
+
+  private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes()
+  private val ROUTES_HANDLER: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, JMAP_API_ROUTE))
+
+  private val REQUEST_OBJECT: String =
+    new Serializer().serialize(RequestObject(Seq(coreIdentifier), Seq(invocation1))).toString()
+
+  private val REQUEST_OBJECT_WITH_UNSUPPORTED_METHOD: String =
+    new Serializer().serialize(RequestObject(Seq(coreIdentifier), Seq(invocation1, unsupportedInvocation))).toString()
+
+  private val RESPONSE_OBJECT: String = new Serializer().serialize(responseObject1).toString()
+  private val RESPONSE_OBJECT_WITH_UNSUPPORTED_METHOD: String = new Serializer().serialize(responseObjectWithUnsupportedMethod).toString()
+
+  var jmapServer: JMAPServer = _
+
+  before {
+    jmapServer = new JMAPServer(TEST_CONFIGURATION, ROUTES_HANDLER)
+    jmapServer.start()
+
+    RestAssured.requestSpecification = new RequestSpecBuilder()
+      .setContentType(ContentType.JSON)
+      .setAccept(ContentType.JSON)
+      .setConfig(newConfig.encoderConfig(encoderConfig.defaultContentCharset(StandardCharsets.UTF_8)))
+      .setPort(jmapServer.getPort.getValue)
+      .setBasePath(JMAP)
+      .build
+  }
+
+  after {
+    jmapServer.stop()
+  }
+
+  "RFC-8621 version, GET" should "not supported and return 404 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .when()
+        .get
+      .then
+        .statusCode(HttpStatus.SC_NOT_FOUND)
+  }
+
+  "RFC-8621 version, POST, without body" should "return 200 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+      .when()
+        .post
+      .then
+        .statusCode(HttpStatus.SC_OK)
+  }
+
+  "RFC-8621 version, POST, methods include supported" should "return OK status" in {
+    val response = RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(REQUEST_OBJECT)
+      .when()
+        .post()
+      .then
+        .statusCode(HttpStatus.SC_OK)
+        .contentType(ContentType.JSON)
+      .extract()
+        .body()
+        .asString()
+
+    response shouldBe (RESPONSE_OBJECT)
+  }
+
+  "RFC-8621 version, POST, with methods" should "return OK status, ResponseObject depend on method" in {
+
+    val response = RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(REQUEST_OBJECT_WITH_UNSUPPORTED_METHOD)
+      .when()
+        .post()
+      .then
+        .statusCode(HttpStatus.SC_OK)
+        .contentType(ContentType.JSON)
+      .extract()
+        .body()
+        .asString()
+
+    response shouldBe (RESPONSE_OBJECT_WITH_UNSUPPORTED_METHOD)
+  }
+
+  "Draft version, GET" should "return 404 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_DRAFT_VERSION_HEADER)
+      .when()
+        .get
+      .then
+        .statusCode(HttpStatus.SC_NOT_FOUND)
+  }
+
+  "Draft version, POST, without body" should "return 400 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_DRAFT_VERSION_HEADER)
+      .when()
+        .post
+      .then
+        .statusCode(HttpStatus.SC_NOT_FOUND)
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 08/13: JAMES-2888: implement Echo method

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 c6979ccad9e77550742d0fc1a59c03c296796fe3
Author: ducnv <du...@gmail.com>
AuthorDate: Mon Mar 23 15:42:38 2020 +0700

    JAMES-2888: implement Echo method
---
 .../james/jmap/rfc/api/method/EchoMethod.scala     | 48 +++++++++++++++++++++
 .../jmap/rfc/api/parser/RequestObjectParser.scala  | 16 +++++++
 .../james/jmap/rfc/api/routes/JMAPAPIRoute.scala   | 13 ++++++
 .../james/jmap/rfc/api/routes/JMAPApiRoutes.scala  | 50 ++++++++++++++++++++++
 .../james/jmap/rfc/model/ResponseObject.scala      | 38 ++++++++++++++++
 .../james/jmap/rfc/api/method/EchoMethodTest.scala | 26 +++++++++++
 6 files changed, 191 insertions(+)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/method/EchoMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/method/EchoMethod.scala
new file mode 100644
index 0000000..fabc9cc
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/method/EchoMethod.scala
@@ -0,0 +1,48 @@
+/** **************************************************************
+ * 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.rfc.api.method
+
+import eu.timepit.refined.auto._
+import org.apache.james.jmap.rfc.api.parser.RequestObjectParser
+import org.apache.james.jmap.rfc.api.routes.{JMAPAPIRoute, MethodName}
+import org.apache.james.jmap.rfc.model.{RequestObject, ResponseObject}
+import org.reactivestreams.Publisher
+import reactor.core.publisher.Mono
+import reactor.netty.http.server.HttpServerRequest
+
+class EchoMethod extends JMAPAPIRoute[ResponseObject] {
+
+  private val requestObjectParser: RequestObjectParser = new RequestObjectParser()
+
+  def toResponseObject(requestObject: RequestObject): ResponseObject = {
+    ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls)
+  }
+
+  override var methodName = MethodName("echoMethod")
+
+  override def process(httpServerRequest: HttpServerRequest): Publisher[ResponseObject] = {
+    httpServerRequest
+      .receive()
+      .asInputStream()
+      .flatMap(requestObjectParser.toRequestObject)
+      .flatMap((requestObject: RequestObject) => {
+        return Mono.just(new ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls))
+      })
+  }
+}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/parser/RequestObjectParser.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/parser/RequestObjectParser.scala
new file mode 100644
index 0000000..154d6da
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/parser/RequestObjectParser.scala
@@ -0,0 +1,16 @@
+package org.apache.james.jmap.rfc.api.parser
+
+import java.io.InputStream
+
+import org.apache.james.jmap.rfc.model.RequestObject
+import play.api.libs.json.{JsError, JsSuccess, Json}
+import reactor.core.publisher.Mono
+
+class RequestObjectParser {
+  def toRequestObject(inputStream: InputStream): Mono[RequestObject] = {
+    Json.fromJson[RequestObject](Json.parse(inputStream)) match {
+      case JsSuccess(requestObject, _) => Mono.just(requestObject)
+      case JsError(errors) => Mono.error(new RuntimeException(errors.toString()))
+    }
+  }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPAPIRoute.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPAPIRoute.scala
new file mode 100644
index 0000000..d505795
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPAPIRoute.scala
@@ -0,0 +1,13 @@
+package org.apache.james.jmap.rfc.api.routes
+
+import eu.timepit.refined.api.Refined
+import eu.timepit.refined.collection.NonEmpty
+import org.reactivestreams.Publisher
+import reactor.netty.http.server.HttpServerRequest
+
+case class MethodName(value: String Refined NonEmpty)
+trait JMAPAPIRoute[T] {
+  var methodName: MethodName
+  def process(httpServerRequest: HttpServerRequest):Publisher[T]
+}
+
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPApiRoutes.scala
new file mode 100644
index 0000000..973ca77
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPApiRoutes.scala
@@ -0,0 +1,50 @@
+/** **************************************************************
+ * 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.rfc.api.routes
+
+import io.netty.handler.codec.http.HttpResponseStatus
+import org.apache.james.jmap.rfc.api.method.EchoMethod
+import reactor.core.publisher.Mono
+import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
+
+object JMAPApiRoutes {
+  private val ECHO_METHOD = new EchoMethod();
+  private val METHOD_NAME_PARAMETER = "method-name"
+}
+
+class JMAPApiRoutes {
+  def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): Mono[Void] = {
+    Mono.just(httpServerRequest)
+      .flatMap(httpRequest => this.process(httpRequest, httpServerResponse))
+      .`then`()
+  }
+
+  def process(httpRequest: HttpServerRequest, httpServerResponse: HttpServerResponse) = {
+    httpRequest.param(JMAPApiRoutes.METHOD_NAME_PARAMETER) match {
+      case JMAPApiRoutes.ECHO_METHOD.methodName.value.value => Mono.just(httpServerResponse
+        .status(HttpResponseStatus.OK)
+        .sendObject(JMAPApiRoutes.ECHO_METHOD.process(httpRequest))).`then`()
+
+      case _ => Mono.just(httpServerResponse
+        .status(HttpResponseStatus.NOT_IMPLEMENTED)
+        .sendObject("Api not implemented")).`then`()
+    }
+  }
+}
+
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/model/ResponseObject.scala
new file mode 100644
index 0000000..90ee590
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/model/ResponseObject.scala
@@ -0,0 +1,38 @@
+/** **************************************************************
+ * 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.rfc.model
+
+import eu.timepit.refined.types.string.NonEmptyString
+import org.apache.james.jmap.rfc.model.ResponseObject.SessionState
+import play.api.libs.json.{JsResult, Json}
+
+case class ResponseObject(sessionState: SessionState, methodResponses: Seq[Invocation])
+
+object ResponseObject {
+
+  case class SessionState(value: NonEmptyString)
+
+  implicit val sessionStateFormat = Json.valueFormat[SessionState]
+  implicit val responseObjectFormat = Json.format[ResponseObject]
+
+  def deserialize(input: String): JsResult[ResponseObject] = {
+    Json.parse(input).validate[ResponseObject]
+  }
+}
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/rfc/api/method/EchoMethodTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/rfc/api/method/EchoMethodTest.scala
new file mode 100644
index 0000000..564745f
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/rfc/api/method/EchoMethodTest.scala
@@ -0,0 +1,26 @@
+/** **************************************************************
+ * 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.rfc.api.method
+
+import org.scalatestplus.play.PlaySpec
+
+class EchoMethodTest extends PlaySpec {
+  "EchoMethod succeed" must {
+  }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 12/13: JAMES-2888: fix error when rebase master

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 2dbba2dc7b1877f99a8bfd360b23099fce422831
Author: ducnv <du...@gmail.com>
AuthorDate: Fri Apr 10 16:13:50 2020 +0700

    JAMES-2888: fix error when rebase master
---
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 32 ++++++++++++----------
 1 file changed, 18 insertions(+), 14 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index b4277ed..4301319 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -32,7 +32,7 @@ import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap.json.Fixture._
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.model.RequestObject
-import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutesHandler, JMAPServer, Version}
+import org.apache.james.jmap.{JMAPConfiguration, JMAPRoutesHandler, JMAPServer, Version, VersionParser}
 import org.scalatest.BeforeAndAfter
 import org.scalatest.flatspec.AnyFlatSpec
 import org.scalatest.matchers.should.Matchers
@@ -41,8 +41,8 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
 
   private val TEST_CONFIGURATION: JMAPConfiguration = JMAPConfiguration.builder().enable().randomPort().build()
   private val ACCEPT_JMAP_VERSION_HEADER = "application/json; jmapVersion="
-  private val ACCEPT_DRAFT_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.DRAFT.getVersion
-  private val ACCEPT_RFC8621_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.RFC8621.getVersion
+  private val ACCEPT_DRAFT_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.DRAFT.asString()
+  private val ACCEPT_RFC8621_VERSION_HEADER = ACCEPT_JMAP_VERSION_HEADER + Version.RFC8621.asString()
 
   private val JMAP_API_ROUTE: JMAPApiRoutes = new JMAPApiRoutes()
   private val ROUTES_HANDLER: ImmutableSet[JMAPRoutesHandler] = ImmutableSet.of(new JMAPRoutesHandler(Version.RFC8621, JMAP_API_ROUTE))
@@ -56,20 +56,24 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
   private val RESPONSE_OBJECT: String = new Serializer().serialize(responseObject1).toString()
   private val RESPONSE_OBJECT_WITH_UNSUPPORTED_METHOD: String = new Serializer().serialize(responseObjectWithUnsupportedMethod).toString()
 
-  private val test: String = """
-                               |{
-                               |  "using": [ "urn:ietf:params:jmap:core"],
-                               |  "methodCalls": {
-                               |      "arg1": "arg1data",
-                               |      "arg2": "arg2data"
-                               |    }
-                               |}
-                               |""".stripMargin
+  private val SUPPORTED_VERSIONS = ImmutableSet.of(Version.DRAFT, Version.RFC8621)
+
+  private val WRONG_OBJECT_REQUEST: String =
+    """
+      |{
+      |  "using": [ "urn:ietf:params:jmap:core"],
+      |  "methodCalls": {
+      |      "arg1": "arg1data",
+      |      "arg2": "arg2data"
+      |    }
+      |}
+      |""".stripMargin
 
   var jmapServer: JMAPServer = _
 
   before {
-    jmapServer = new JMAPServer(TEST_CONFIGURATION, ROUTES_HANDLER)
+    val versionParser: VersionParser = new VersionParser(SUPPORTED_VERSIONS)
+    jmapServer = new JMAPServer(TEST_CONFIGURATION, ROUTES_HANDLER, versionParser)
     jmapServer.start()
 
     RestAssured.requestSpecification = new RequestSpecBuilder()
@@ -164,7 +168,7 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
     RestAssured
       .`given`()
         .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
-        .body(test)
+        .body(WRONG_OBJECT_REQUEST)
       .when()
         .post
       .then


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 11/13: JAMES-2888: add handle IllegalAgrumentsEception and test

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 749c65c874277018877d31851f3738e1f3e045db
Author: ducnv <du...@gmail.com>
AuthorDate: Thu Apr 9 15:37:20 2020 +0700

    JAMES-2888: add handle IllegalAgrumentsEception and test
---
 .../jmap/draft/exceptions/BadRequestException.java | 30 ----------------------
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   | 18 ++++++++++---
 .../james/jmap/routes/JMAPApiRoutesTest.scala      | 21 +++++++++++++++
 3 files changed, 35 insertions(+), 34 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/BadRequestException.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/BadRequestException.java
deleted file mode 100644
index 172c8ae..0000000
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/BadRequestException.java
+++ /dev/null
@@ -1,30 +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.draft.exceptions;
-
-public class BadRequestException extends RuntimeException {
-
-    public BadRequestException(String message) {
-        super(message);
-    }
-
-    public BadRequestException(String message, Throwable cause) {
-        super(message, cause);
-    }
-}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
index 8336487..7637fa1 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -27,6 +27,7 @@ import eu.timepit.refined.auto._
 import io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE
 import io.netty.handler.codec.http.HttpMethod
 import io.netty.handler.codec.http.HttpResponseStatus.OK
+import org.apache.http.HttpStatus.SC_BAD_REQUEST
 import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE
 import org.apache.james.jmap.JMAPUrls.JMAP
 import org.apache.james.jmap.json.Serializer
@@ -42,8 +43,6 @@ import reactor.core.scheduler.Schedulers
 import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
 
 class JMAPApiRoutes extends JMAPRoutes {
-  override def logger(): Logger = LoggerFactory.getLogger(getClass)
-
   private val coreEcho = new CoreEcho
 
   override def routes(): stream.Stream[JMAPRoute] = Stream.of(
@@ -59,7 +58,7 @@ class JMAPApiRoutes extends JMAPRoutes {
   private def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): Mono[Void] =
     this.requestAsJsonStream(httpServerRequest)
       .flatMap(requestObject => this.process(requestObject, httpServerResponse))
-      .onErrorResume(throwable => SMono.fromPublisher(handleInternalError(httpServerResponse, throwable)))
+      .onErrorResume(throwable => handleError(throwable, httpServerResponse))
       .subscribeOn(Schedulers.elastic)
       .asJava()
       .`then`()
@@ -75,7 +74,7 @@ class JMAPApiRoutes extends JMAPRoutes {
   private def parseRequestObject(inputStream: InputStream): SMono[RequestObject] =
     new Serializer().deserializeRequestObject(inputStream) match {
       case JsSuccess(requestObject, _) => SMono.just(requestObject)
-      case JsError(errors) => SMono.raiseError(new RuntimeException(errors.toString()))
+      case JsError(_) => SMono.raiseError(new IllegalArgumentException("Invalid RequestObject"))
     }
 
   private def process(requestObject: RequestObject, httpServerResponse: HttpServerResponse): SMono[Void] =
@@ -101,4 +100,15 @@ class JMAPApiRoutes extends JMAPRoutes {
       Arguments(Json.obj("type" -> "Not implemented")),
       invocation.methodCallId))
   }
+
+  private def handleError(throwable: Throwable, httpServerResponse: HttpServerResponse): SMono[Void] = {
+    if (throwable.isInstanceOf[IllegalArgumentException]) {
+      return SMono.fromPublisher(httpServerResponse.status(SC_BAD_REQUEST)
+        .header(CONTENT_TYPE, JSON_CONTENT_TYPE)
+        .sendString(SMono.fromCallable(() => throwable.getMessage), StandardCharsets.UTF_8)
+        .`then`())
+    }
+
+    SMono.fromPublisher(handleInternalError(httpServerResponse, throwable))
+  }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
index 55bf125..b4277ed 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/routes/JMAPApiRoutesTest.scala
@@ -56,6 +56,16 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
   private val RESPONSE_OBJECT: String = new Serializer().serialize(responseObject1).toString()
   private val RESPONSE_OBJECT_WITH_UNSUPPORTED_METHOD: String = new Serializer().serialize(responseObjectWithUnsupportedMethod).toString()
 
+  private val test: String = """
+                               |{
+                               |  "using": [ "urn:ietf:params:jmap:core"],
+                               |  "methodCalls": {
+                               |      "arg1": "arg1data",
+                               |      "arg2": "arg2data"
+                               |    }
+                               |}
+                               |""".stripMargin
+
   var jmapServer: JMAPServer = _
 
   before {
@@ -149,4 +159,15 @@ class JMAPApiRoutesTest extends AnyFlatSpec with BeforeAndAfter with Matchers {
       .then
         .statusCode(HttpStatus.SC_NOT_FOUND)
   }
+
+  "RFC-8621 version, POST, with wrong requestObject body" should "return 400 status" in {
+    RestAssured
+      .`given`()
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(test)
+      .when()
+        .post
+      .then
+        .statusCode(HttpStatus.SC_BAD_REQUEST)
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 01/13: JAMES-3041 Change the way we control resources for Spooler mail processing

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 d6c0a6b283161d4ee3b2f0d0f6f04804e9feb881
Author: Matthieu Baechler <ma...@apache.org>
AuthorDate: Tue Feb 18 15:47:05 2020 +0100

    JAMES-3041 Change the way we control resources for Spooler mail processing
    
        For now, we controlled resources using a bounded threadpool
        for mail processing.
    
        However, it makes the concurrency of the dequeuing harder
        to achieve and doesn't leverage modern tools Reactor brings
        like explicit control over concurrency for a given Flux.
    
        With this commit we start using elastic Scheduler everywhere
        and control resources by specifying concurrency parameter.
---
 .../mailetcontainer/impl/JamesMailSpooler.java     | 53 ++++++++--------------
 .../mailetcontainer/impl/JamesMailSpoolerTest.java |  5 +-
 .../remote/delivery/RemoteDeliveryTest.java        |  2 +-
 .../SetMessagesOutboxFlagUpdateTest.java           |  4 +-
 .../routes/MailRepositoriesRoutesTest.java         |  2 +-
 .../webadmin/service/ReprocessingServiceTest.java  |  2 +-
 .../apache/james/queue/api/MailQueueFactory.java   | 47 ++++++++++++++++++-
 .../james/queue/api/MailQueueFactoryTest.java      | 26 +++++++++++
 .../api/ManageableMailQueueFactoryContract.java    |  6 +--
 .../james/queue/file/FileMailQueueFactory.java     |  4 +-
 .../queue/library/AbstractMailQueueFactory.java    |  6 +--
 .../james/queue/memory/MemoryMailQueueFactory.java |  6 +--
 .../MemoryCacheableMailQueueFactoryTest.java       |  6 +--
 .../org/apache/james/queue/rabbitmq/Dequeuer.java  | 11 +++--
 .../queue/rabbitmq/RabbitMQMailQueueFactory.java   | 22 ++++-----
 15 files changed, 130 insertions(+), 72 deletions(-)

diff --git a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
index 28383cd..aaa86d7 100644
--- a/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
+++ b/server/mailet/mailetcontainer-camel/src/main/java/org/apache/james/mailetcontainer/impl/JamesMailSpooler.java
@@ -22,7 +22,6 @@ package org.apache.james.mailetcontainer.impl;
 import static org.apache.james.metrics.api.TimeMetric.ExecutionResult.DEFAULT_100_MS_THRESHOLD;
 
 import java.io.IOException;
-import java.util.concurrent.Executors;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import javax.annotation.PostConstruct;
@@ -41,14 +40,12 @@ import org.apache.james.metrics.api.TimeMetric;
 import org.apache.james.queue.api.MailQueue;
 import org.apache.james.queue.api.MailQueue.MailQueueItem;
 import org.apache.james.queue.api.MailQueueFactory;
-import org.apache.james.util.concurrent.NamedThreadFactory;
 import org.apache.mailet.Mail;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
-import reactor.core.scheduler.Scheduler;
 import reactor.core.scheduler.Schedulers;
 
 /**
@@ -62,9 +59,10 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
     public static final String SPOOL_PROCESSING = "spoolProcessing";
 
     /**
-     * The number of threads used to move mail through the spool.
+     * concurrency level to use for dequeuing mails from spool, allows to throttle resources dedicated to that async
+     * process.
      */
-    private int numThreads;
+    private int concurrencyLevel;
 
     private final AtomicInteger processingActive = new AtomicInteger(0);
 
@@ -77,11 +75,8 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
 
     private final MailQueueFactory<?> queueFactory;
     private reactor.core.Disposable disposable;
-    private Scheduler spooler;
-    private int parallelismLevel;
     private MailQueue queue;
 
-
     @Inject
     public JamesMailSpooler(MetricFactory metricFactory, MailProcessor mailProcessor, MailQueueFactory<?> queueFactory) {
         this.metricFactory = metricFactory;
@@ -91,10 +86,7 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
 
     @Override
     public void configure(HierarchicalConfiguration<ImmutableNode> config) {
-        numThreads = config.getInt("threads", 100);
-        //Reactor helps us run things in parallel but we have to ensure there are always threads available
-        //in the threadpool to avoid starvation.
-        parallelismLevel = Math.max(1, numThreads - 2);
+        concurrencyLevel = config.getInt("threads", 100);
     }
 
     /**
@@ -103,18 +95,17 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
     @PostConstruct
     public void init() {
         LOGGER.info("init...");
-        queue = queueFactory.createQueue(MailQueueFactory.SPOOL);
-        spooler = Schedulers.fromExecutor(Executors.newFixedThreadPool(numThreads, NamedThreadFactory.withName("spooler")));
-        LOGGER.info("uses {} Thread(s)", numThreads);
-        run();
+        LOGGER.info("Concurrency level is {}", concurrencyLevel);
+        queue = queueFactory.createQueue(MailQueueFactory.SPOOL, MailQueueFactory.prefetchCount(concurrencyLevel));
+        disposable = run(queue);
+        LOGGER.info("Spooler started");
     }
 
-    private void run() {
-        LOGGER.info("Queue={}", queue);
-        disposable = Flux.from(queue.deQueue())
-            .flatMap(item -> handleOnQueueItem(item).subscribeOn(spooler), parallelismLevel)
+    private reactor.core.Disposable run(MailQueue queue) {
+        return Flux.from(queue.deQueue())
+            .flatMap(item -> handleOnQueueItem(item).subscribeOn(Schedulers.elastic()), concurrencyLevel)
             .onErrorContinue((throwable, item) -> LOGGER.error("Exception processing mail while spooling {}", item, throwable))
-            .subscribeOn(spooler)
+            .subscribeOn(Schedulers.elastic())
             .subscribe();
     }
 
@@ -122,7 +113,7 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
         TimeMetric timeMetric = metricFactory.timer(SPOOL_PROCESSING);
         try {
             return Mono.fromCallable(processingActive::incrementAndGet)
-                .flatMap(ignore -> processMail(queueItem).subscribeOn(spooler))
+                .flatMap(ignore -> processMail(queueItem))
                 .doOnSuccess(any -> timeMetric.stopAndPublish().logWhenExceedP99(DEFAULT_100_MS_THRESHOLD))
                 .doOnSuccess(any -> processingActive.decrementAndGet());
         } catch (Throwable e) {
@@ -134,16 +125,12 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
         return Mono
             .using(
                 queueItem::getMail,
-                resource -> Mono.just(resource)
-                    .doOnNext(mail -> LOGGER.debug("==== Begin processing mail {} ====", mail.getName()))
-                    .map(mail -> performProcessMail(queueItem, mail))
-                    .doOnNext(mail -> LOGGER.debug("==== End processing mail {} ====", mail.getName())),
-                LifecycleUtil::dispose)
-            .then();
+                mail -> Mono.fromRunnable(() -> performProcessMail(queueItem, mail)),
+                LifecycleUtil::dispose);
     }
 
-
-    private Mail performProcessMail(MailQueueItem queueItem, Mail mail) {
+    private void performProcessMail(MailQueueItem queueItem, Mail mail) {
+        LOGGER.debug("==== Begin processing mail {} ====", mail.getName());
         try {
             mailProcessor.service(mail);
 
@@ -157,8 +144,9 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
             } catch (MailQueue.MailQueueException ex) {
                 throw new RuntimeException(e);
             }
+        } finally {
+            LOGGER.debug("==== End processing mail {} ====", mail.getName());
         }
-        return mail;
     }
 
     /**
@@ -174,7 +162,6 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
     public void dispose() {
         LOGGER.info("start dispose() ...");
         disposable.dispose();
-        spooler.dispose();
         try {
             queue.close();
         } catch (IOException e) {
@@ -185,7 +172,7 @@ public class JamesMailSpooler implements Disposable, Configurable, MailSpoolerMB
 
     @Override
     public int getThreadCount() {
-        return numThreads;
+        return concurrencyLevel;
     }
 
     @Override
diff --git a/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailSpoolerTest.java b/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailSpoolerTest.java
index 0b62199..9485686 100644
--- a/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailSpoolerTest.java
+++ b/server/mailet/mailetcontainer-camel/src/test/java/org/apache/james/mailetcontainer/impl/JamesMailSpoolerTest.java
@@ -23,6 +23,7 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS;
 import static org.awaitility.Duration.TEN_SECONDS;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
@@ -73,7 +74,7 @@ class JamesMailSpoolerTest {
         MailQueue queue = mock(MailQueue.class);
         workQueue.onNext(item);
         when(queue.deQueue()).thenAnswer(any -> workQueue.limitRate(1).filter(MockedMailQueueItem::isNotDone));
-        when(queueFactory.createQueue(MailQueueFactory.SPOOL)).thenAnswer(any -> queue);
+        when(queueFactory.createQueue(eq(MailQueueFactory.SPOOL), any())).thenAnswer(any -> queue);
 
         doThrow(new RuntimeException("Arbitrary failure"))
             .doNothing()
@@ -107,7 +108,7 @@ class JamesMailSpoolerTest {
         MailQueue queue = mock(MailQueue.class);
         workQueue.onNext(item);
         when(queue.deQueue()).thenAnswer(any -> workQueue.limitRate(1).filter(MockedMailQueueItem::isNotDone));
-        when(queueFactory.createQueue(MailQueueFactory.SPOOL)).thenAnswer(any -> queue);
+        when(queueFactory.createQueue(eq(MailQueueFactory.SPOOL), any())).thenAnswer(any -> queue);
 
         doAnswer(ignored -> {
             Thread.currentThread().interrupt();
diff --git a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/RemoteDeliveryTest.java b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/RemoteDeliveryTest.java
index 1e62160..cfcc648 100644
--- a/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/RemoteDeliveryTest.java
+++ b/server/mailet/mailets/src/test/java/org/apache/james/transport/mailets/remote/delivery/RemoteDeliveryTest.java
@@ -99,7 +99,7 @@ public class RemoteDeliveryTest {
 
     @Before
     public void setUp() throws ConfigurationException {
-        MailQueueFactory<ManageableMailQueue> queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        MailQueueFactory<? extends ManageableMailQueue> queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
         mailQueue = queueFactory.createQueue(RemoteDeliveryConfiguration.DEFAULT_OUTGOING_QUEUE_NAME);
         DNSService dnsService = mock(DNSService.class);
         MemoryDomainList domainList = new MemoryDomainList(dnsService);
diff --git a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesOutboxFlagUpdateTest.java b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesOutboxFlagUpdateTest.java
index 0f82362..bf21d1e 100644
--- a/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesOutboxFlagUpdateTest.java
+++ b/server/protocols/jmap-draft-integration-testing/jmap-draft-integration-testing-common/src/test/java/org/apache/james/jmap/draft/methods/integration/SetMessagesOutboxFlagUpdateTest.java
@@ -73,12 +73,12 @@ public abstract class SetMessagesOutboxFlagUpdateTest {
 
     protected MailQueueFactory<MailQueue> noopMailQueueFactory = new MailQueueFactory<MailQueue>() {
         @Override
-        public Optional<MailQueue> getQueue(MailQueueName name) {
+        public Optional<MailQueue> getQueue(MailQueueName name, PrefetchCount prefetchCount) {
             return Optional.of(createQueue(name));
         }
 
         @Override
-        public MailQueue createQueue(MailQueueName name) {
+        public MailQueue createQueue(MailQueueName name, PrefetchCount prefetchCount) {
             return new MailQueue() {
                 @Override
                 public void close() throws IOException {
diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java b/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java
index 613c506..b69d674 100644
--- a/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java
+++ b/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/routes/MailRepositoriesRoutesTest.java
@@ -114,7 +114,7 @@ public class MailRepositoriesRoutesTest {
 
         MemoryTaskManager taskManager = new MemoryTaskManager(new Hostname("foo"));
         JsonTransformer jsonTransformer = new JsonTransformer();
-        MailQueueFactory<ManageableMailQueue> queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
+        MailQueueFactory<? extends ManageableMailQueue> queueFactory = new MemoryMailQueueFactory(new RawMailQueueItemDecoratorFactory());
         spoolQueue = queueFactory.createQueue(MailQueueFactory.SPOOL);
         customQueue = queueFactory.createQueue(CUSTOM_QUEUE);
 
diff --git a/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/service/ReprocessingServiceTest.java b/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/service/ReprocessingServiceTest.java
index 994c7aa..02cbc0d 100644
--- a/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/service/ReprocessingServiceTest.java
+++ b/server/protocols/webadmin/webadmin-mailrepository/src/test/java/org/apache/james/webadmin/service/ReprocessingServiceTest.java
@@ -64,7 +64,7 @@ public class ReprocessingServiceTest {
 
     private ReprocessingService reprocessingService;
     private MemoryMailRepositoryStore mailRepositoryStore;
-    private MailQueueFactory<ManageableMailQueue> queueFactory;
+    private MailQueueFactory<? extends ManageableMailQueue> queueFactory;
     private FakeMail mail1;
     private FakeMail mail2;
     private FakeMail mail3;
diff --git a/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueueFactory.java b/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueueFactory.java
index 3f0a5dc..35a4967 100644
--- a/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueueFactory.java
+++ b/server/queue/queue-api/src/main/java/org/apache/james/queue/api/MailQueueFactory.java
@@ -22,11 +22,46 @@ package org.apache.james.queue.api;
 import java.util.Optional;
 import java.util.Set;
 
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+
 /**
  * Factory for {@link MailQueue}
  */
 public interface MailQueueFactory<T extends MailQueue> {
 
+    static PrefetchCount defaultPrefetchCount() {
+        return prefetchCount(5);
+    }
+
+    static PrefetchCount prefetchCount(int count) {
+        return new PrefetchCount(count);
+    }
+
+    /**
+     * {@link PrefetchCount} provides producers insights about what kind of load consumers expect.
+     * If you expect to consume the mailqueue with 10 concurrent workers, it's a good idea to
+     * ensure the producer will fill the stream with at least 10 elements.
+     */
+    class PrefetchCount {
+        private final int value;
+
+        @VisibleForTesting
+        PrefetchCount(int value) {
+            Preconditions.checkArgument(value >= 0, "only non-negative values are allowed");
+            this.value = value;
+        }
+
+        public int asInt() {
+            return value;
+        }
+
+        @Override
+        public String toString() {
+            return "PrefetchCount = " + value;
+        }
+    }
+
     /**
      * {@link MailQueue} which is used for spooling the messages
      */
@@ -38,9 +73,17 @@ public interface MailQueueFactory<T extends MailQueue> {
      * @param name
      * @return queue
      */
-    Optional<T> getQueue(MailQueueName name);
+    default Optional<T> getQueue(MailQueueName name) {
+        return getQueue(name, defaultPrefetchCount());
+    }
+
+    Optional<T> getQueue(MailQueueName name, PrefetchCount count);
+
+    default T createQueue(MailQueueName name) {
+        return createQueue(name, defaultPrefetchCount());
+    }
 
-    T createQueue(MailQueueName name);
+    T createQueue(MailQueueName name, PrefetchCount count);
 
     Set<MailQueueName> listCreatedMailQueues();
 }
diff --git a/server/queue/queue-api/src/test/java/org/apache/james/queue/api/MailQueueFactoryTest.java b/server/queue/queue-api/src/test/java/org/apache/james/queue/api/MailQueueFactoryTest.java
new file mode 100644
index 0000000..c565603
--- /dev/null
+++ b/server/queue/queue-api/src/test/java/org/apache/james/queue/api/MailQueueFactoryTest.java
@@ -0,0 +1,26 @@
+package org.apache.james.queue.api;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+class MailQueueFactoryTest {
+
+    @Test
+    void prefetchCountShouldNotBeNegative() {
+        Assertions.assertThatThrownBy(() -> new MailQueueFactory.PrefetchCount(-12))
+            .isInstanceOf(IllegalArgumentException.class);
+    }
+
+    @Test
+    void prefetchCountCouldBeZero() {
+        Assertions.assertThatCode(() -> new MailQueueFactory.PrefetchCount(0)).doesNotThrowAnyException();
+    }
+
+    @Test
+    void prefetchCountCouldBePositive() {
+        Assertions.assertThatCode(() -> new MailQueueFactory.PrefetchCount(12)).doesNotThrowAnyException();
+    }
+
+}
\ No newline at end of file
diff --git a/server/queue/queue-api/src/test/java/org/apache/james/queue/api/ManageableMailQueueFactoryContract.java b/server/queue/queue-api/src/test/java/org/apache/james/queue/api/ManageableMailQueueFactoryContract.java
index 5ae065f..fc671b8 100644
--- a/server/queue/queue-api/src/test/java/org/apache/james/queue/api/ManageableMailQueueFactoryContract.java
+++ b/server/queue/queue-api/src/test/java/org/apache/james/queue/api/ManageableMailQueueFactoryContract.java
@@ -25,15 +25,15 @@ import javax.mail.MessagingException;
 
 import org.junit.jupiter.api.Test;
 
-public interface ManageableMailQueueFactoryContract {
+public interface ManageableMailQueueFactoryContract<T extends ManageableMailQueue> {
 
     MailQueueName NAME_1 = MailQueueName.of("name1");
 
-    MailQueueFactory<ManageableMailQueue> getMailQueueFactory();
+    MailQueueFactory<T> getMailQueueFactory();
 
     @Test
     default void createMailQueueShouldNotConflictIfAlreadyExists() throws MessagingException {
-        MailQueueFactory<ManageableMailQueue> mailQueueFactory = getMailQueueFactory();
+        MailQueueFactory<T> mailQueueFactory = getMailQueueFactory();
         MailQueue firstCreation = mailQueueFactory.createQueue(NAME_1);
 
         firstCreation.enQueue(Mails.defaultMail().name("name").build());
diff --git a/server/queue/queue-file/src/main/java/org/apache/james/queue/file/FileMailQueueFactory.java b/server/queue/queue-file/src/main/java/org/apache/james/queue/file/FileMailQueueFactory.java
index 0587620..f727bcd 100644
--- a/server/queue/queue-file/src/main/java/org/apache/james/queue/file/FileMailQueueFactory.java
+++ b/server/queue/queue-file/src/main/java/org/apache/james/queue/file/FileMailQueueFactory.java
@@ -76,12 +76,12 @@ public class FileMailQueueFactory implements MailQueueFactory<ManageableMailQueu
     }
 
     @Override
-    public Optional<ManageableMailQueue> getQueue(MailQueueName name) {
+    public Optional<ManageableMailQueue> getQueue(MailQueueName name, PrefetchCount prefetchCount) {
         return Optional.ofNullable(queues.get(name));
     }
 
     @Override
-    public ManageableMailQueue createQueue(MailQueueName name) {
+    public ManageableMailQueue createQueue(MailQueueName name, PrefetchCount prefetchCount) {
         return queues.computeIfAbsent(name, mailQueueName -> {
             try {
                 return new FileCacheableMailQueue(mailQueueActionItemDecoratorFactory, fs.getFile("file://var/store/queue"), mailQueueName, sync);
diff --git a/server/queue/queue-jms/src/main/java/org/apache/james/queue/library/AbstractMailQueueFactory.java b/server/queue/queue-jms/src/main/java/org/apache/james/queue/library/AbstractMailQueueFactory.java
index 5fa22ef..052b0d2 100644
--- a/server/queue/queue-jms/src/main/java/org/apache/james/queue/library/AbstractMailQueueFactory.java
+++ b/server/queue/queue-jms/src/main/java/org/apache/james/queue/library/AbstractMailQueueFactory.java
@@ -96,13 +96,13 @@ public abstract class AbstractMailQueueFactory<T extends MailQueue> implements M
     }
 
     @Override
-    public final synchronized Optional<T> getQueue(MailQueueName name) {
+    public final synchronized Optional<T> getQueue(MailQueueName name, PrefetchCount prefetchCount) {
         return Optional.ofNullable(queues.get(name));
     }
 
     @Override
-    public synchronized T createQueue(MailQueueName name) {
-        return getQueue(name).orElseGet(() -> createAndRegisterQueue(name));
+    public synchronized T createQueue(MailQueueName name, PrefetchCount prefetchCount) {
+        return getQueue(name, prefetchCount).orElseGet(() -> createAndRegisterQueue(name));
     }
 
     private T createAndRegisterQueue(MailQueueName name) {
diff --git a/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java b/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java
index e13cb10..da9e8b1 100644
--- a/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java
+++ b/server/queue/queue-memory/src/main/java/org/apache/james/queue/memory/MemoryMailQueueFactory.java
@@ -58,7 +58,7 @@ import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 import reactor.core.scheduler.Schedulers;
 
-public class MemoryMailQueueFactory implements MailQueueFactory<ManageableMailQueue> {
+public class MemoryMailQueueFactory implements MailQueueFactory<MemoryMailQueueFactory.MemoryCacheableMailQueue> {
 
     private final ConcurrentHashMap<MailQueueName, MemoryCacheableMailQueue> mailQueues;
     private final MailQueueItemDecoratorFactory mailQueueItemDecoratorFactory;
@@ -78,12 +78,12 @@ public class MemoryMailQueueFactory implements MailQueueFactory<ManageableMailQu
     }
 
     @Override
-    public Optional<ManageableMailQueue> getQueue(MailQueueName name) {
+    public Optional<MemoryCacheableMailQueue> getQueue(MailQueueName name, PrefetchCount count) {
         return Optional.ofNullable(mailQueues.get(name));
     }
 
     @Override
-    public MemoryCacheableMailQueue createQueue(MailQueueName name) {
+    public MemoryCacheableMailQueue createQueue(MailQueueName name, PrefetchCount prefetchCount) {
         return mailQueues.computeIfAbsent(name, mailQueueName -> new MemoryCacheableMailQueue(mailQueueName, mailQueueItemDecoratorFactory));
     }
 
diff --git a/server/queue/queue-memory/src/test/java/org/apache/james/queue/memory/MemoryCacheableMailQueueFactoryTest.java b/server/queue/queue-memory/src/test/java/org/apache/james/queue/memory/MemoryCacheableMailQueueFactoryTest.java
index aa2e294..a50722b 100644
--- a/server/queue/queue-memory/src/test/java/org/apache/james/queue/memory/MemoryCacheableMailQueueFactoryTest.java
+++ b/server/queue/queue-memory/src/test/java/org/apache/james/queue/memory/MemoryCacheableMailQueueFactoryTest.java
@@ -21,12 +21,12 @@ package org.apache.james.queue.memory;
 
 import org.apache.james.queue.api.MailQueueFactory;
 import org.apache.james.queue.api.MailQueueFactoryContract;
-import org.apache.james.queue.api.ManageableMailQueue;
 import org.apache.james.queue.api.ManageableMailQueueFactoryContract;
 import org.apache.james.queue.api.RawMailQueueItemDecoratorFactory;
 import org.junit.jupiter.api.BeforeEach;
 
-class MemoryCacheableMailQueueFactoryTest implements MailQueueFactoryContract<ManageableMailQueue>, ManageableMailQueueFactoryContract {
+class MemoryCacheableMailQueueFactoryTest implements MailQueueFactoryContract<MemoryMailQueueFactory.MemoryCacheableMailQueue>,
+    ManageableMailQueueFactoryContract<MemoryMailQueueFactory.MemoryCacheableMailQueue> {
 
     MemoryMailQueueFactory memoryMailQueueFactory;
 
@@ -36,7 +36,7 @@ class MemoryCacheableMailQueueFactoryTest implements MailQueueFactoryContract<Ma
     }
 
     @Override
-    public MailQueueFactory<ManageableMailQueue> getMailQueueFactory() {
+    public MailQueueFactory<MemoryMailQueueFactory.MemoryCacheableMailQueue> getMailQueueFactory() {
         return memoryMailQueueFactory;
     }
 }
\ No newline at end of file
diff --git a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Dequeuer.java b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Dequeuer.java
index 64c0c93..b22d850 100644
--- a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Dequeuer.java
+++ b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/Dequeuer.java
@@ -30,6 +30,7 @@ import org.apache.james.backends.rabbitmq.ReceiverProvider;
 import org.apache.james.metrics.api.Metric;
 import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.queue.api.MailQueue;
+import org.apache.james.queue.api.MailQueueFactory;
 import org.apache.james.queue.rabbitmq.view.api.DeleteCondition;
 import org.apache.james.queue.rabbitmq.view.api.MailQueueView;
 import org.apache.mailet.Mail;
@@ -39,13 +40,13 @@ import com.rabbitmq.client.Delivery;
 
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
+import reactor.core.scheduler.Schedulers;
 import reactor.rabbitmq.AcknowledgableDelivery;
 import reactor.rabbitmq.ConsumeOptions;
 import reactor.rabbitmq.Receiver;
 
 class Dequeuer implements Closeable {
     private static final boolean REQUEUE = true;
-    private static final int EXECUTION_RATE = 5;
 
     private static class RabbitMQMailQueueItem implements MailQueue.MailQueueItem {
 
@@ -84,14 +85,14 @@ class Dequeuer implements Closeable {
 
     Dequeuer(MailQueueName name, ReceiverProvider receiverProvider, Function<MailReferenceDTO, MailWithEnqueueId> mailLoader,
              MailReferenceSerializer serializer, MetricFactory metricFactory,
-             MailQueueView mailQueueView) {
+             MailQueueView mailQueueView, MailQueueFactory.PrefetchCount prefetchCount) {
         this.mailLoader = mailLoader;
         this.mailReferenceSerializer = serializer;
         this.mailQueueView = mailQueueView;
         this.dequeueMetric = metricFactory.generate(DEQUEUED_METRIC_NAME_PREFIX + name.asString());
         this.receiver = receiverProvider.createReceiver();
         this.flux = this.receiver
-            .consumeManualAck(name.toWorkQueueName().asString(), new ConsumeOptions().qos(EXECUTION_RATE))
+            .consumeManualAck(name.toWorkQueueName().asString(), new ConsumeOptions().qos(prefetchCount.asInt()))
             .filter(getResponse -> getResponse.getBody() != null);
     }
 
@@ -101,8 +102,8 @@ class Dequeuer implements Closeable {
     }
 
     Flux<? extends MailQueue.MailQueueItem> deQueue() {
-        return flux.concatMap(this::loadItem)
-            .concatMap(this::filterIfDeleted);
+        return flux.flatMapSequential(response -> loadItem(response).subscribeOn(Schedulers.elastic()))
+            .concatMap(item -> filterIfDeleted(item).subscribeOn(Schedulers.elastic()));
     }
 
     private Mono<RabbitMQMailQueueItem> filterIfDeleted(RabbitMQMailQueueItem item) {
diff --git a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueFactory.java b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueFactory.java
index 3805e74..fc2f784 100644
--- a/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueFactory.java
+++ b/server/queue/queue-rabbitmq/src/main/java/org/apache/james/queue/rabbitmq/RabbitMQMailQueueFactory.java
@@ -93,7 +93,7 @@ public class RabbitMQMailQueueFactory implements MailQueueFactory<RabbitMQMailQu
             this.configuration = configuration;
         }
 
-        RabbitMQMailQueue create(MailQueueName mailQueueName) {
+        RabbitMQMailQueue create(MailQueueName mailQueueName, PrefetchCount prefetchCount) {
             MailQueueView mailQueueView = mailQueueViewFactory.create(mailQueueName);
             mailQueueView.initialize(mailQueueName);
 
@@ -103,7 +103,7 @@ public class RabbitMQMailQueueFactory implements MailQueueFactory<RabbitMQMailQu
                 new Enqueuer(mailQueueName, sender, mimeMessageStore, mailReferenceSerializer,
                     metricFactory, mailQueueView, clock),
                 new Dequeuer(mailQueueName, receiverProvider, mailLoader, mailReferenceSerializer,
-                    metricFactory, mailQueueView),
+                    metricFactory, mailQueueView, prefetchCount),
                 mailQueueView,
                 decoratorFactory);
 
@@ -133,15 +133,15 @@ public class RabbitMQMailQueueFactory implements MailQueueFactory<RabbitMQMailQu
     }
 
     @Override
-    public Optional<RabbitMQMailQueue> getQueue(org.apache.james.queue.api.MailQueueName name) {
-        return getQueueFromRabbitServer(MailQueueName.fromString(name.asString()));
+    public Optional<RabbitMQMailQueue> getQueue(org.apache.james.queue.api.MailQueueName name, PrefetchCount count) {
+        return getQueueFromRabbitServer(MailQueueName.fromString(name.asString()), count);
     }
 
     @Override
-    public RabbitMQMailQueue createQueue(org.apache.james.queue.api.MailQueueName name) {
+    public RabbitMQMailQueue createQueue(org.apache.james.queue.api.MailQueueName name, PrefetchCount count) {
         MailQueueName mailQueueName = MailQueueName.fromString(name.asString());
-        return getQueueFromRabbitServer(mailQueueName)
-            .orElseGet(() -> createQueueIntoRabbitServer(mailQueueName));
+        return getQueueFromRabbitServer(mailQueueName, count)
+            .orElseGet(() -> createQueueIntoRabbitServer(mailQueueName, count));
     }
 
     @Override
@@ -152,7 +152,7 @@ public class RabbitMQMailQueueFactory implements MailQueueFactory<RabbitMQMailQu
             .collect(ImmutableSet.toImmutableSet());
     }
 
-    private RabbitMQMailQueue createQueueIntoRabbitServer(MailQueueName mailQueueName) {
+    private RabbitMQMailQueue createQueueIntoRabbitServer(MailQueueName mailQueueName, PrefetchCount prefetchCount) {
         String exchangeName = mailQueueName.toRabbitExchangeName().asString();
         Flux.concat(
             sender.declareExchange(ExchangeSpecification.exchange(exchangeName)
@@ -169,13 +169,13 @@ public class RabbitMQMailQueueFactory implements MailQueueFactory<RabbitMQMailQu
                 .routingKey(EMPTY_ROUTING_KEY)))
             .then()
             .block();
-        return privateFactory.create(mailQueueName);
+        return privateFactory.create(mailQueueName, prefetchCount);
     }
 
-    private Optional<RabbitMQMailQueue> getQueueFromRabbitServer(MailQueueName name) {
+    private Optional<RabbitMQMailQueue> getQueueFromRabbitServer(MailQueueName name, PrefetchCount prefetchCount) {
         return mqManagementApi.listCreatedMailQueueNames()
             .filter(name::equals)
-            .map(privateFactory::create)
+            .map(queueName -> privateFactory.create(queueName, prefetchCount))
             .findFirst();
     }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 03/13: JAMES-2891 moving common classes into a common module

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 5d7b4e4634140988368c4b2d2104444c5e9809b9
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Fri Mar 27 11:01:34 2020 +0700

    JAMES-2891 moving common classes into a common module
---
 pom.xml                                                           | 5 +++++
 .../java/org/apache/james/jmap/http/AuthenticationRoutes.java     | 2 +-
 .../src/main/java/org/apache/james/jmap/http/DownloadRoutes.java  | 2 +-
 .../src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java   | 2 +-
 .../src/main/java/org/apache/james/jmap/http/UploadRoutes.java    | 2 +-
 .../test/java/org/apache/james/jmap/http/AuthenticatorTest.java   | 2 +-
 .../http/QueryParameterAccessTokenAuthenticationStrategyTest.java | 2 --
 server/protocols/jmap/pom.xml                                     | 8 ++++++++
 .../jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java      | 4 ++--
 .../org/apache/james/jmap}/exceptions/UnauthorizedException.java  | 2 +-
 .../java/org/apache/james/jmap/http/AuthenticationStrategy.java   | 0
 .../src/main/java/org/apache/james/jmap/http/Authenticator.java   | 2 +-
 12 files changed, 22 insertions(+), 11 deletions(-)

diff --git a/pom.xml b/pom.xml
index ac8afa0..a30cb9b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1516,6 +1516,11 @@
             </dependency>
             <dependency>
                 <groupId>${james.groupId}</groupId>
+                <artifactId>james-server-jmap</artifactId>
+                <version>${project.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>${james.groupId}</groupId>
                 <artifactId>james-server-jmap-draft</artifactId>
                 <version>${project.version}</version>
             </dependency>
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index 70b0490..f824da2 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -52,13 +52,13 @@ import org.apache.james.jmap.draft.api.SimpleTokenFactory;
 import org.apache.james.jmap.draft.api.SimpleTokenManager;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
 import org.apache.james.jmap.draft.json.MultipleObjectMapperBuilder;
 import org.apache.james.jmap.draft.model.AccessTokenRequest;
 import org.apache.james.jmap.draft.model.AccessTokenResponse;
 import org.apache.james.jmap.draft.model.ContinuationTokenRequest;
 import org.apache.james.jmap.draft.model.ContinuationTokenResponse;
 import org.apache.james.jmap.draft.model.EndPointsResponse;
+import org.apache.james.jmap.exceptions.UnauthorizedException;
 import org.apache.james.metrics.api.MetricFactory;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index 8ac0bc3..21ac5a9 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -43,9 +43,9 @@ import org.apache.james.jmap.JMAPRoutes;
 import org.apache.james.jmap.draft.api.SimpleTokenFactory;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
 import org.apache.james.jmap.draft.model.AttachmentAccessToken;
 import org.apache.james.jmap.draft.utils.DownloadPath;
+import org.apache.james.jmap.exceptions.UnauthorizedException;
 import org.apache.james.mailbox.BlobManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.exception.BlobNotFoundException;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index 7454afe..68c51c4 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -36,11 +36,11 @@ import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
 import org.apache.james.jmap.draft.methods.RequestHandler;
 import org.apache.james.jmap.draft.model.AuthenticatedRequest;
 import org.apache.james.jmap.draft.model.InvocationRequest;
 import org.apache.james.jmap.draft.model.InvocationResponse;
+import org.apache.james.jmap.exceptions.UnauthorizedException;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.metrics.api.MetricFactory;
 import org.slf4j.Logger;
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index 6788c69..5629608 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -40,8 +40,8 @@ import org.apache.james.jmap.JMAPRoute;
 import org.apache.james.jmap.JMAPRoutes;
 import org.apache.james.jmap.draft.exceptions.BadRequestException;
 import org.apache.james.jmap.draft.exceptions.InternalErrorException;
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
 import org.apache.james.jmap.draft.model.UploadResponse;
+import org.apache.james.jmap.exceptions.UnauthorizedException;
 import org.apache.james.mailbox.AttachmentManager;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.model.Attachment;
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/AuthenticatorTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/AuthenticatorTest.java
index 9f31ec7..0d614ed 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/AuthenticatorTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/AuthenticatorTest.java
@@ -31,7 +31,7 @@ import org.apache.james.core.Username;
 import org.apache.james.jmap.api.access.AccessToken;
 import org.apache.james.jmap.api.access.AccessTokenRepository;
 import org.apache.james.jmap.draft.exceptions.MailboxSessionCreationException;
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
+import org.apache.james.jmap.exceptions.UnauthorizedException;
 import org.apache.james.jmap.memory.access.MemoryAccessTokenRepository;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.metrics.tests.RecordingMetricFactory;
diff --git a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/QueryParameterAccessTokenAuthenticationStrategyTest.java b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/QueryParameterAccessTokenAuthenticationStrategyTest.java
index ad06819..3895c58 100644
--- a/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/QueryParameterAccessTokenAuthenticationStrategyTest.java
+++ b/server/protocols/jmap-draft/src/test/java/org/apache/james/jmap/http/QueryParameterAccessTokenAuthenticationStrategyTest.java
@@ -19,12 +19,10 @@
 package org.apache.james.jmap.http;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
 import org.apache.james.jmap.draft.api.SimpleTokenManager;
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
 import org.apache.james.mailbox.MailboxManager;
 import org.junit.Before;
 import org.junit.Test;
diff --git a/server/protocols/jmap/pom.xml b/server/protocols/jmap/pom.xml
index e7b7e0b..d39f9e5 100644
--- a/server/protocols/jmap/pom.xml
+++ b/server/protocols/jmap/pom.xml
@@ -32,6 +32,10 @@
     <dependencies>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>apache-james-mailbox-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>james-core</artifactId>
         </dependency>
         <dependency>
@@ -44,6 +48,10 @@
         </dependency>
         <dependency>
             <groupId>${james.groupId}</groupId>
+            <artifactId>metrics-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>${james.groupId}</groupId>
             <artifactId>testing-base</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
index f2a8b09..3ef6302 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
@@ -48,12 +48,12 @@ public interface JMAPRoutes {
         return response.status(INTERNAL_SERVER_ERROR).send();
     }
 
-    default Mono<Void> handleBadRequest(HttpServerResponse response, Exception e) {
+    default Mono<Void> handleBadRequest(HttpServerResponse response, Throwable e) {
         logger().warn("Invalid request received.", e);
         return response.status(BAD_REQUEST).send();
     }
 
-    default Mono<Void> handleAuthenticationFailure(HttpServerResponse response, Exception e) {
+    default Mono<Void> handleAuthenticationFailure(HttpServerResponse response, Throwable e) {
         logger().warn("Unauthorized", e);
         return response.status(UNAUTHORIZED).send();
     }
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/UnauthorizedException.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/UnauthorizedException.java
similarity index 96%
rename from server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/UnauthorizedException.java
rename to server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/UnauthorizedException.java
index 464abe3..b9fd26a 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/draft/exceptions/UnauthorizedException.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/exceptions/UnauthorizedException.java
@@ -17,7 +17,7 @@
  * under the License.                                           *
  ****************************************************************/
 
-package org.apache.james.jmap.draft.exceptions;
+package org.apache.james.jmap.exceptions;
 
 public class UnauthorizedException extends RuntimeException {
     public UnauthorizedException() {
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationStrategy.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/http/AuthenticationStrategy.java
similarity index 100%
rename from server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationStrategy.java
rename to server/protocols/jmap/src/main/java/org/apache/james/jmap/http/AuthenticationStrategy.java
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/Authenticator.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/http/Authenticator.java
similarity index 97%
rename from server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/Authenticator.java
rename to server/protocols/jmap/src/main/java/org/apache/james/jmap/http/Authenticator.java
index 20ca13a..6f9bc98 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/Authenticator.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/http/Authenticator.java
@@ -22,7 +22,7 @@ import java.util.List;
 
 import javax.inject.Inject;
 
-import org.apache.james.jmap.draft.exceptions.UnauthorizedException;
+import org.apache.james.jmap.exceptions.UnauthorizedException;
 import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.metrics.api.MetricFactory;
 import org.slf4j.Logger;


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 06/13: JAMES-2891 Remove JMAPRoutes.logger()

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 d481581f9dcd3c7c012160c60066fb086754fc5f
Author: Tran Tien Duc <dt...@linagora.com>
AuthorDate: Tue Apr 7 15:31:10 2020 +0700

    JAMES-2891 Remove JMAPRoutes.logger()
---
 .../org/apache/james/jmap/http/AuthenticationRoutes.java    | 13 ++++---------
 .../java/org/apache/james/jmap/http/DownloadRoutes.java     |  9 ++-------
 .../main/java/org/apache/james/jmap/http/JMAPApiRoutes.java |  9 ++-------
 .../main/java/org/apache/james/jmap/http/UploadRoutes.java  |  7 +------
 .../scala/org/apache/james/jmap/http/SessionRoutes.scala    |  5 ++---
 .../main/scala/org/apache/james/jmap/model/Session.scala    |  6 ++----
 .../apache/james/jmap/json/SessionSerializationTest.scala   | 10 ++--------
 .../src/main/java/org/apache/james/jmap/JMAPRoutes.java     | 10 ++++------
 .../src/test/java/org/apache/james/jmap/JMAPServerTest.java |  5 -----
 9 files changed, 19 insertions(+), 55 deletions(-)

diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
index f824da2..a968480 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/AuthenticationRoutes.java
@@ -100,11 +100,6 @@ public class AuthenticationRoutes implements JMAPRoutes {
     }
 
     @Override
-    public Logger logger() {
-        return LOGGER;
-    }
-
-    @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
             JMAPRoute.builder()
@@ -141,7 +136,7 @@ public class AuthenticationRoutes implements JMAPRoutes {
                         throw new RuntimeException(objectRequest.getClass() + " " + objectRequest);
                     }
                 })))
-            .onErrorResume(BadRequestException.class, e -> handleBadRequest(response, e))
+            .onErrorResume(BadRequestException.class, e -> handleBadRequest(response, LOGGER, e))
             .doOnEach(logOnError(e -> LOGGER.error("Unexpected error", e)))
             .onErrorResume(e -> handleInternalError(response, e))
             .subscriberContext(jmapContext(request))
@@ -153,10 +148,10 @@ public class AuthenticationRoutes implements JMAPRoutes {
             return authenticator.authenticate(req)
                 .flatMap(session -> returnEndPointsResponse(resp)
                     .subscriberContext(jmapAuthContext(session)))
-                .onErrorResume(BadRequestException.class, e -> handleBadRequest(resp, e))
+                .onErrorResume(BadRequestException.class, e -> handleBadRequest(resp, LOGGER, e))
                 .doOnEach(logOnError(e -> LOGGER.error("Unexpected error", e)))
                 .onErrorResume(InternalErrorException.class, e -> handleInternalError(resp, e))
-                .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(resp, e))
+                .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(resp, LOGGER, e))
                 .subscriberContext(jmapContext(req))
                 .subscriberContext(jmapAction("returnEndPoints"))
                 .subscribeOn(Schedulers.elastic());
@@ -186,7 +181,7 @@ public class AuthenticationRoutes implements JMAPRoutes {
             .flatMap(session -> Mono.from(accessTokenManager.revoke(AccessToken.fromString(authorizationHeader)))
                     .then(resp.status(NO_CONTENT).send().then())
                 .subscriberContext(jmapAuthContext(session)))
-            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(resp, e))
+            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(resp, LOGGER, e))
             .subscriberContext(jmapContext(req))
             .subscriberContext(jmapAction("auth-delete"))
             .subscribeOn(Schedulers.elastic());
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
index 21ac5a9..160bdde 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/DownloadRoutes.java
@@ -93,11 +93,6 @@ public class DownloadRoutes implements JMAPRoutes {
     }
 
     @Override
-    public Logger logger() {
-        return LOGGER;
-    }
-
-    @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
             JMAPRoute.builder()
@@ -145,7 +140,7 @@ public class DownloadRoutes implements JMAPRoutes {
             .flatMap(session -> Mono.from(metricFactory.runPublishingTimerMetric("JMAP-download-post",
                     respondAttachmentAccessToken(session, downloadPath, response)))
                 .subscriberContext(jmapAuthContext(session)))
-            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, e))
+            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, LOGGER, e))
             .doOnEach(logOnError(e -> LOGGER.error("Unexpected error", e)))
             .onErrorResume(e -> handleInternalError(response, e))
             .subscriberContext(jmapContext(request))
@@ -175,7 +170,7 @@ public class DownloadRoutes implements JMAPRoutes {
             .flatMap(session -> Mono.from(metricFactory.runPublishingTimerMetric("JMAP-download-get",
                     download(session, downloadPath, response)))
                 .subscriberContext(jmapAuthContext(session)))
-            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, e))
+            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, LOGGER, e))
             .doOnEach(logOnError(e -> LOGGER.error("Unexpected error", e)))
             .onErrorResume(e -> handleInternalError(response, e))
             .subscriberContext(jmapContext(request))
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
index 68c51c4..deea975 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/JMAPApiRoutes.java
@@ -80,11 +80,6 @@ public class JMAPApiRoutes implements JMAPRoutes {
     }
 
     @Override
-    public Logger logger() {
-        return LOGGER;
-    }
-
-    @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
             JMAPRoute.builder()
@@ -106,8 +101,8 @@ public class JMAPApiRoutes implements JMAPRoutes {
                 .then(Mono.from(metricFactory.runPublishingTimerMetric("JMAP-request",
                     post(request, response, session))))
                 .subscriberContext(jmapAuthContext(session)))
-            .onErrorResume(BadRequestException.class, e -> handleBadRequest(response, e))
-            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, e))
+            .onErrorResume(BadRequestException.class, e -> handleBadRequest(response, LOGGER, e))
+            .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, LOGGER, e))
             .doOnEach(logOnError(e -> LOGGER.error("Unexpected error", e)))
             .onErrorResume(e -> handleInternalError(response, e))
             .subscriberContext(jmapContext(request))
diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
index 5629608..5605b13 100644
--- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
+++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/http/UploadRoutes.java
@@ -82,11 +82,6 @@ public class UploadRoutes implements JMAPRoutes {
     }
 
     @Override
-    public Logger logger() {
-        return LOGGER;
-    }
-
-    @Override
     public Stream<JMAPRoute> routes() {
         return Stream.of(
             JMAPRoute.builder()
@@ -110,7 +105,7 @@ public class UploadRoutes implements JMAPRoutes {
                     .subscriberContext(jmapAuthContext(session)))
                 .onErrorResume(CancelledUploadException.class, e -> handleCanceledUpload(response, e))
                 .onErrorResume(BadRequestException.class, e -> handleBadRequest(response, e))
-                .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, e))
+                .onErrorResume(UnauthorizedException.class, e -> handleAuthenticationFailure(response, LOGGER, e))
                 .doOnEach(logOnError(e -> LOGGER.error("Unexpected error", e)))
                 .onErrorResume(e -> handleInternalError(response, e))
                 .subscriberContext(jmapContext(request))
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
index 75a280b..901ef64 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/http/SessionRoutes.scala
@@ -27,7 +27,7 @@ import javax.inject.Inject
 import org.apache.james.jmap.HttpConstants.JSON_CONTENT_TYPE_UTF8
 import org.apache.james.jmap.JMAPRoutes
 import org.apache.james.jmap.exceptions.UnauthorizedException
-import org.apache.james.jmap.http.SessionRoutes.JMAP_SESSION
+import org.apache.james.jmap.http.SessionRoutes.{JMAP_SESSION, LOGGER}
 import org.apache.james.jmap.json.Serializer
 import org.apache.james.jmap.model.Session
 import org.reactivestreams.Publisher
@@ -48,7 +48,6 @@ class SessionRoutes(val authFilter: Authenticator,
                     val sessionSupplier: SessionSupplier = new SessionSupplier(),
                     val serializer: Serializer = new Serializer()) extends JMAPRoutes {
 
-  val logger: Logger = SessionRoutes.LOGGER
   private val generateSession: BiFunction[HttpServerRequest, HttpServerResponse, Publisher[Void]] =
     (request, response) => SMono.fromPublisher(authFilter.authenticate(request))
       .map(_.getUser)
@@ -69,7 +68,7 @@ class SessionRoutes(val authFilter: Authenticator,
 
   def errorHandling(throwable: Throwable, response: HttpServerResponse): Mono[Void] =
     throwable match {
-      case _: UnauthorizedException => handleAuthenticationFailure(response, throwable)
+      case _: UnauthorizedException => handleAuthenticationFailure(response, LOGGER, throwable)
       case _ => handleInternalError(response, throwable)
     }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala
index 0bb733d..1b09946 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/model/Session.scala
@@ -61,10 +61,8 @@ object Account {
            isPersonal: IsPersonal,
            isReadOnly: IsReadOnly,
            accountCapabilities: Set[_ <: Capability]): Either[IllegalArgumentException, Account] =
-    AccountId.from(name) match {
-      case Left(ex: IllegalArgumentException) => Left(ex)
-      case Right(accountId) => Right(new Account(accountId, name, isPersonal, isReadOnly, accountCapabilities))
-    }
+    AccountId.from(name)
+      .map(Account(_, name, isPersonal, isReadOnly, accountCapabilities))
 
   def unapplyIgnoreAccountId(account: Account): Some[(Username, IsPersonal, IsReadOnly, Set[_ <: Capability])] =
     Some(account.name, account.isPersonal, account.isReadOnly, account.accountCapabilities)
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
index 98e3f81..248c331 100644
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/json/SessionSerializationTest.scala
@@ -89,19 +89,13 @@ object SessionSerializationTest {
     name = USER_1,
     isPersonal = IS_PERSONAL,
     isReadOnly = IS_NOT_READ_ONLY,
-    accountCapabilities = Set(CORE_CAPABILITY)) match {
-      case Left(ex: IllegalArgumentException) => throw ex
-      case Right(account: Account) => account
-    }
+    accountCapabilities = Set(CORE_CAPABILITY)).toOption.get
 
   private val ACCOUNT_2: Account = Account.from(
     name = USER_2,
     isPersonal = IS_NOT_PERSONAL,
     isReadOnly = IS_NOT_READ_ONLY,
-    accountCapabilities = Set(CORE_CAPABILITY))  match {
-      case Left(ex: IllegalArgumentException) => throw ex
-      case Right(account: Account) => account
-    }
+    accountCapabilities = Set(CORE_CAPABILITY)).toOption.get
 
   private val PRIMARY_ACCOUNTS = Map(
     MAIL_IDENTIFIER -> ACCOUNT_1.accountId,
diff --git a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
index 3ef6302..f683ea1 100644
--- a/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
+++ b/server/protocols/jmap/src/main/java/org/apache/james/jmap/JMAPRoutes.java
@@ -42,19 +42,17 @@ public interface JMAPRoutes {
             .header("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept"));
     }
 
-    Logger logger();
-
     default Mono<Void> handleInternalError(HttpServerResponse response, Throwable e) {
         return response.status(INTERNAL_SERVER_ERROR).send();
     }
 
-    default Mono<Void> handleBadRequest(HttpServerResponse response, Throwable e) {
-        logger().warn("Invalid request received.", e);
+    default Mono<Void> handleBadRequest(HttpServerResponse response, Logger logger, Throwable e) {
+        logger.warn("Invalid request received.", e);
         return response.status(BAD_REQUEST).send();
     }
 
-    default Mono<Void> handleAuthenticationFailure(HttpServerResponse response, Throwable e) {
-        logger().warn("Unauthorized", e);
+    default Mono<Void> handleAuthenticationFailure(HttpServerResponse response, Logger logger, Throwable e) {
+        logger.warn("Unauthorized", e);
         return response.status(UNAUTHORIZED).send();
     }
 }
diff --git a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
index 992e622..6fd242b 100644
--- a/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
+++ b/server/protocols/jmap/src/test/java/org/apache/james/jmap/JMAPServerTest.java
@@ -242,11 +242,6 @@ class JMAPServerTest {
                     .noCorsHeaders());
         }
 
-        @Override
-        public Logger logger() {
-            return LOGGER;
-        }
-
         private Mono<Void> sendVersionResponse(HttpServerResponse response) {
             return response.status(HttpResponseStatus.OK)
                 .header(CONTENT_TYPE, JSON_CONTENT_TYPE_UTF8)


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org


[james-project] 09/13: JAMES-2888: Finish work, add testcase, rebase from master

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 b5679e9485d99c1110ea2db1628ac3d7a080c8fb
Author: ducnv <du...@gmail.com>
AuthorDate: Fri Mar 27 16:47:10 2020 +0700

    JAMES-2888: Finish work, add testcase, rebase from master
---
 .../james/jmap/rfc/api/method/EchoMethod.scala     | 48 ---------------
 .../jmap/rfc/api/parser/RequestObjectParser.scala  | 16 -----
 .../james/jmap/rfc/api/routes/JMAPAPIRoute.scala   | 13 ----
 .../james/jmap/rfc/api/routes/JMAPApiRoutes.scala  | 50 ----------------
 .../apache/james/jmap/routes/JMAPApiRoutes.scala   | 69 ++++++++++++++++++++++
 .../apache/james/jmap/method/CoreEchoTest.scala}   | 32 ++++++----
 .../james/jmap/rfc/api/method/EchoMethodTest.scala | 26 --------
 7 files changed, 89 insertions(+), 165 deletions(-)

diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/method/EchoMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/method/EchoMethod.scala
deleted file mode 100644
index fabc9cc..0000000
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/method/EchoMethod.scala
+++ /dev/null
@@ -1,48 +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.rfc.api.method
-
-import eu.timepit.refined.auto._
-import org.apache.james.jmap.rfc.api.parser.RequestObjectParser
-import org.apache.james.jmap.rfc.api.routes.{JMAPAPIRoute, MethodName}
-import org.apache.james.jmap.rfc.model.{RequestObject, ResponseObject}
-import org.reactivestreams.Publisher
-import reactor.core.publisher.Mono
-import reactor.netty.http.server.HttpServerRequest
-
-class EchoMethod extends JMAPAPIRoute[ResponseObject] {
-
-  private val requestObjectParser: RequestObjectParser = new RequestObjectParser()
-
-  def toResponseObject(requestObject: RequestObject): ResponseObject = {
-    ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls)
-  }
-
-  override var methodName = MethodName("echoMethod")
-
-  override def process(httpServerRequest: HttpServerRequest): Publisher[ResponseObject] = {
-    httpServerRequest
-      .receive()
-      .asInputStream()
-      .flatMap(requestObjectParser.toRequestObject)
-      .flatMap((requestObject: RequestObject) => {
-        return Mono.just(new ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls))
-      })
-  }
-}
\ No newline at end of file
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/parser/RequestObjectParser.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/parser/RequestObjectParser.scala
deleted file mode 100644
index 154d6da..0000000
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/parser/RequestObjectParser.scala
+++ /dev/null
@@ -1,16 +0,0 @@
-package org.apache.james.jmap.rfc.api.parser
-
-import java.io.InputStream
-
-import org.apache.james.jmap.rfc.model.RequestObject
-import play.api.libs.json.{JsError, JsSuccess, Json}
-import reactor.core.publisher.Mono
-
-class RequestObjectParser {
-  def toRequestObject(inputStream: InputStream): Mono[RequestObject] = {
-    Json.fromJson[RequestObject](Json.parse(inputStream)) match {
-      case JsSuccess(requestObject, _) => Mono.just(requestObject)
-      case JsError(errors) => Mono.error(new RuntimeException(errors.toString()))
-    }
-  }
-}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPAPIRoute.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPAPIRoute.scala
deleted file mode 100644
index d505795..0000000
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPAPIRoute.scala
+++ /dev/null
@@ -1,13 +0,0 @@
-package org.apache.james.jmap.rfc.api.routes
-
-import eu.timepit.refined.api.Refined
-import eu.timepit.refined.collection.NonEmpty
-import org.reactivestreams.Publisher
-import reactor.netty.http.server.HttpServerRequest
-
-case class MethodName(value: String Refined NonEmpty)
-trait JMAPAPIRoute[T] {
-  var methodName: MethodName
-  def process(httpServerRequest: HttpServerRequest):Publisher[T]
-}
-
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPApiRoutes.scala
deleted file mode 100644
index 973ca77..0000000
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/api/routes/JMAPApiRoutes.scala
+++ /dev/null
@@ -1,50 +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.rfc.api.routes
-
-import io.netty.handler.codec.http.HttpResponseStatus
-import org.apache.james.jmap.rfc.api.method.EchoMethod
-import reactor.core.publisher.Mono
-import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
-
-object JMAPApiRoutes {
-  private val ECHO_METHOD = new EchoMethod();
-  private val METHOD_NAME_PARAMETER = "method-name"
-}
-
-class JMAPApiRoutes {
-  def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): Mono[Void] = {
-    Mono.just(httpServerRequest)
-      .flatMap(httpRequest => this.process(httpRequest, httpServerResponse))
-      .`then`()
-  }
-
-  def process(httpRequest: HttpServerRequest, httpServerResponse: HttpServerResponse) = {
-    httpRequest.param(JMAPApiRoutes.METHOD_NAME_PARAMETER) match {
-      case JMAPApiRoutes.ECHO_METHOD.methodName.value.value => Mono.just(httpServerResponse
-        .status(HttpResponseStatus.OK)
-        .sendObject(JMAPApiRoutes.ECHO_METHOD.process(httpRequest))).`then`()
-
-      case _ => Mono.just(httpServerResponse
-        .status(HttpResponseStatus.NOT_IMPLEMENTED)
-        .sendObject("Api not implemented")).`then`()
-    }
-  }
-}
-
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
new file mode 100644
index 0000000..ddfe2fe
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/JMAPApiRoutes.scala
@@ -0,0 +1,69 @@
+/** **************************************************************
+ * 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.routes
+
+import eu.timepit.refined.auto._
+import org.apache.james.jmap.json.Serializer
+import org.apache.james.jmap.method.CoreEcho
+import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
+import org.apache.james.jmap.model.{Invocation, RequestObject, ResponseObject}
+import org.reactivestreams.Publisher
+import play.api.libs.json.{JsError, JsSuccess, Json}
+import reactor.core.scala.publisher.SMono
+import reactor.netty.http.server.{HttpServerRequest, HttpServerResponse}
+
+object JMAPApiRoutes {
+  private val ECHO_METHOD = new CoreEcho()
+}
+
+class JMAPApiRoutes {
+  private val echoMethod = JMAPApiRoutes.ECHO_METHOD
+
+  def post(httpServerRequest: HttpServerRequest, httpServerResponse: HttpServerResponse): SMono[Void] = {
+    SMono.fromPublisher(extractRequestObject(httpServerRequest))
+      .flatMap(this.process)
+      .doOnError(e => new RuntimeException(e.getMessage))
+      .`then`()
+  }
+
+  private def process(requestObject: RequestObject): SMono[ResponseObject] = {
+    SMono.just(
+      requestObject.methodCalls.map((invocation: Invocation) =>
+        invocation.methodName match {
+          case echoMethod.methodName => echoMethod.process(invocation)
+          case _ => SMono.just(new Invocation(
+            MethodName("error"),
+            Arguments(Json.obj("type" -> "Not implemented")),
+            invocation.methodCallId))
+        }
+      )
+    ).flatMap((invocations: Seq[Invocation]) => SMono.just(ResponseObject(ResponseObject.SESSION_STATE, invocations)))
+  }
+
+  private def extractRequestObject(httpServerRequest: HttpServerRequest): Publisher[RequestObject] = {
+    httpServerRequest
+      .receive()
+      .asInputStream()
+      .flatMap(inputStream => new Serializer().deserializeRequestObject(inputStream) match {
+        case JsSuccess(requestObject, _) => SMono.just(new ResponseObject(ResponseObject.SESSION_STATE, requestObject.methodCalls))
+        case JsError(errors) => SMono.raiseError(new RuntimeException(errors.toString()))
+      })
+  }
+}
+
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/model/ResponseObject.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
similarity index 52%
rename from server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/model/ResponseObject.scala
rename to server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
index 90ee590..b723321 100644
--- a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/rfc/model/ResponseObject.scala
+++ b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/method/CoreEchoTest.scala
@@ -16,23 +16,31 @@
  * specific language governing permissions and limitations      *
  * under the License.                                           *
  * ***************************************************************/
+package org.apache.james.jmap.method
 
-package org.apache.james.jmap.rfc.model
+import org.apache.james.jmap.json.Fixture.{invocation1, invocation2}
+import org.apache.james.jmap.model.Invocation
+import org.scalatest.matchers.should.Matchers
+import org.scalatest.wordspec.AnyWordSpec
 
-import eu.timepit.refined.types.string.NonEmptyString
-import org.apache.james.jmap.rfc.model.ResponseObject.SessionState
-import play.api.libs.json.{JsResult, Json}
+class CoreEchoTest extends AnyWordSpec with Matchers {
+  private val echoMethod: CoreEcho = new CoreEcho()
 
-case class ResponseObject(sessionState: SessionState, methodResponses: Seq[Invocation])
+  "CoreEcho" should {
+    "Process" should {
+      "success and return the same with parameters as the invocation request" in {
+        val expectedResponse: Invocation = invocation1
+        val dataResponse = SMono.fromPublisher(echoMethod.process(invocation1)).block()
 
-object ResponseObject {
+        dataResponse shouldBe expectedResponse
+      }
 
-  case class SessionState(value: NonEmptyString)
+      "success and not return anything else different than the original invocation" in {
+        val wrongExpected: Invocation = invocation2
+        val dataResponse = SMono.fromPublisher(echoMethod.process(invocation1)).block()
 
-  implicit val sessionStateFormat = Json.valueFormat[SessionState]
-  implicit val responseObjectFormat = Json.format[ResponseObject]
-
-  def deserialize(input: String): JsResult[ResponseObject] = {
-    Json.parse(input).validate[ResponseObject]
+        dataResponse should not be(wrongExpected)
+      }
+    }
   }
 }
diff --git a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/rfc/api/method/EchoMethodTest.scala b/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/rfc/api/method/EchoMethodTest.scala
deleted file mode 100644
index 564745f..0000000
--- a/server/protocols/jmap-rfc-8621/src/test/scala/org/apache/james/jmap/rfc/api/method/EchoMethodTest.scala
+++ /dev/null
@@ -1,26 +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.rfc.api.method
-
-import org.scalatestplus.play.PlaySpec
-
-class EchoMethodTest extends PlaySpec {
-  "EchoMethod succeed" must {
-  }
-}


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org