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