You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2020/10/14 07:55:09 UTC
[james-project] 01/04: JAMES-3410 Email/set destroy implementation
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 fe29c2febb305a7dd96b8bd22e0f5584b0599587
Author: LanKhuat <dl...@linagora.com>
AuthorDate: Thu Oct 8 13:38:24 2020 +0700
JAMES-3410 Email/set destroy implementation
---
.../rfc8621/contract/EmailSetMethodContract.scala | 130 +++++++++++++++++++++
.../rfc8621/memory/MemoryEmailSetMethodTest.java | 39 +++++++
.../james/jmap/json/EmailSetSerializer.scala | 42 +++++++
.../org/apache/james/jmap/mail/EmailSet.scala | 33 ++++++
.../apache/james/jmap/method/EmailSetMethod.scala | 85 ++++++++++++++
5 files changed, 329 insertions(+)
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
new file mode 100644
index 0000000..17f5bc3
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailSetMethodContract.scala
@@ -0,0 +1,130 @@
+/****************************************************************
+ * 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.rfc8621.contract
+
+import java.nio.charset.StandardCharsets
+import java.time.format.DateTimeFormatter
+import java.util.concurrent.TimeUnit
+
+import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
+import io.restassured.RestAssured.{`given`, requestSpecification}
+import io.restassured.http.ContentType.JSON
+import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import org.apache.http.HttpStatus.SC_OK
+import org.apache.james.GuiceJamesServer
+import org.apache.james.jmap.http.UserCredential
+import org.apache.james.jmap.rfc8621.contract.Fixture.{ACCEPT_RFC8621_VERSION_HEADER, ANDRE, ANDRE_PASSWORD, BOB, BOB_PASSWORD, DOMAIN, authScheme, baseRequestSpecBuilder}
+import org.apache.james.mailbox.MessageManager.AppendCommand
+import org.apache.james.mailbox.model.{MailboxPath, MessageId}
+import org.apache.james.mime4j.dom.Message
+import org.apache.james.modules.MailboxProbeImpl
+import org.apache.james.utils.DataProbeImpl
+import org.awaitility.Awaitility
+import org.awaitility.Duration.ONE_HUNDRED_MILLISECONDS
+import org.junit.jupiter.api.{BeforeEach, Test}
+
+trait EmailSetMethodContract {
+ private lazy val slowPacedPollInterval = ONE_HUNDRED_MILLISECONDS
+ private lazy val calmlyAwait = Awaitility.`with`
+ .pollInterval(slowPacedPollInterval)
+ .and.`with`.pollDelay(slowPacedPollInterval)
+ .await
+ private lazy val awaitAtMostTenSeconds = calmlyAwait.atMost(10, TimeUnit.SECONDS)
+
+ private lazy val UTC_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX")
+
+ @BeforeEach
+ def setUp(server: GuiceJamesServer): Unit = {
+ server.getProbe(classOf[DataProbeImpl])
+ .fluent
+ .addDomain(DOMAIN.asString)
+ .addUser(BOB.asString, BOB_PASSWORD)
+ .addUser(ANDRE.asString, ANDRE_PASSWORD)
+
+ requestSpecification = baseRequestSpecBuilder(server)
+ .setAuth(authScheme(UserCredential(BOB, BOB_PASSWORD)))
+ .build
+ }
+
+ @Test
+ def emailSetShouldDestroyEmail(server: GuiceJamesServer): Unit = {
+ val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+ mailboxProbe.createMailbox(MailboxPath.inbox(BOB))
+ val messageId1: MessageId = mailboxProbe
+ .appendMessage(BOB.asString, MailboxPath.inbox(BOB),
+ AppendCommand.from(
+ buildTestMessage))
+ .getMessageId
+
+ val request =
+ s"""{
+ | "using": [
+ | "urn:ietf:params:jmap:core",
+ | "urn:ietf:params:jmap:mail"],
+ | "methodCalls": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "destroy": ["${messageId1.serialize}"]
+ | }, "c1"],
+ | ["Email/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "ids": ["${messageId1.serialize}"]
+ | }, "c2"]
+ | ]
+ |}""".stripMargin
+
+ val response = `given`
+ .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+ .body(request)
+ .when
+ .post
+ .`then`
+ .statusCode(SC_OK)
+ .contentType(JSON)
+ .extract
+ .body
+ .asString
+
+ assertThatJson(response).isEqualTo(
+ s"""{
+ | "sessionState": "75128aab4b1b",
+ | "methodResponses": [
+ | ["Email/set", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "newState": "000001",
+ | "destroyed": ["${messageId1.serialize}"]
+ | }, "c1"],
+ | ["Email/get", {
+ | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+ | "state": "000001",
+ | "list": [],
+ | "notFound": ["${messageId1.serialize}"]
+ | }, "c2"]
+ | ]
+ |}""".stripMargin)
+ }
+
+ private def buildTestMessage = {
+ Message.Builder
+ .of
+ .setSubject("test")
+ .setBody("testmail", StandardCharsets.UTF_8)
+ .build
+ }
+}
diff --git a/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSetMethodTest.java b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSetMethodTest.java
new file mode 100644
index 0000000..ec7126a
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621-integration-tests/memory-jmap-rfc-8621-integration-tests/src/test/java/org/apache/james/jmap/rfc8621/memory/MemoryEmailSetMethodTest.java
@@ -0,0 +1,39 @@
+/****************************************************************
+ * 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.rfc8621.memory;
+
+import static org.apache.james.MemoryJamesServerMain.IN_MEMORY_SERVER_AGGREGATE_MODULE;
+
+import org.apache.james.GuiceJamesServer;
+import org.apache.james.JamesServerBuilder;
+import org.apache.james.JamesServerExtension;
+import org.apache.james.jmap.rfc8621.contract.EmailSetMethodContract;
+import org.apache.james.modules.TestJMAPServerModule;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+public class MemoryEmailSetMethodTest implements EmailSetMethodContract {
+
+ @RegisterExtension
+ static JamesServerExtension testExtension = new JamesServerBuilder<>(JamesServerBuilder.defaultConfigurationProvider())
+ .server(configuration -> GuiceJamesServer.forConfiguration(configuration)
+ .combineWith(IN_MEMORY_SERVER_AGGREGATE_MODULE)
+ .overrideWith(new TestJMAPServerModule()))
+ .build();
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
new file mode 100644
index 0000000..d4ce747
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/json/EmailSetSerializer.scala
@@ -0,0 +1,42 @@
+/****************************************************************
+ * 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.json
+
+import javax.inject.Inject
+import org.apache.james.jmap.mail.{DestroyIds, EmailSetRequest, EmailSetResponse}
+import org.apache.james.mailbox.model.MessageId
+import play.api.libs.json.{Format, JsError, JsObject, JsResult, JsString, JsSuccess, JsValue, Json, OFormat, Reads, Writes}
+
+class EmailSetSerializer @Inject() (messageIdFactory: MessageId.Factory) {
+
+ private implicit val messageIdWrites: Writes[MessageId] = messageId => JsString(messageId.serialize)
+ private implicit val messageIdReads: Reads[MessageId] = {
+ case JsString(serializedMessageId) => JsSuccess(messageIdFactory.fromString(serializedMessageId))
+ case _ => JsError("Invalid messageId")
+ }
+
+ private implicit val destroyIdsFormat: Format[DestroyIds] = Json.valueFormat[DestroyIds]
+ private implicit val emailRequestSetFormat: Format[EmailSetRequest] = Json.format[EmailSetRequest]
+ private implicit val emailResponseSetFormat: OFormat[EmailSetResponse] = Json.format[EmailSetResponse]
+
+ def deserialize(input: JsValue): JsResult[EmailSetRequest] = Json.fromJson[EmailSetRequest](input)
+
+ def serialize(response: EmailSetResponse): JsObject = Json.toJsObject(response)
+}
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
new file mode 100644
index 0000000..a47ecff
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailSet.scala
@@ -0,0 +1,33 @@
+/****************************************************************
+ * 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.mail
+
+import org.apache.james.jmap.model.AccountId
+import org.apache.james.jmap.model.State.State
+import org.apache.james.mailbox.model.MessageId
+
+case class DestroyIds(value: Seq[MessageId])
+case class EmailSetRequest(accountId: AccountId,
+ destroy: Option[DestroyIds])
+
+case class EmailSetResponse(accountId: AccountId,
+ newState: State,
+ destroyed: Option[DestroyIds])
+
+
diff --git a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
new file mode 100644
index 0000000..9d086e4
--- /dev/null
+++ b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/method/EmailSetMethod.scala
@@ -0,0 +1,85 @@
+/****************************************************************
+ * 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.method
+
+import eu.timepit.refined.auto._
+import javax.inject.Inject
+import org.apache.james.jmap.http.SessionSupplier
+import org.apache.james.jmap.json.{EmailSetSerializer, ResponseSerializer}
+import org.apache.james.jmap.mail.{DestroyIds, EmailSetRequest, EmailSetResponse}
+import org.apache.james.jmap.model.CapabilityIdentifier.CapabilityIdentifier
+import org.apache.james.jmap.model.DefaultCapabilities.{CORE_CAPABILITY, MAIL_CAPABILITY}
+import org.apache.james.jmap.model.Invocation.{Arguments, MethodName}
+import org.apache.james.jmap.model.{Capabilities, Invocation, State}
+import org.apache.james.mailbox.model.DeleteResult
+import org.apache.james.mailbox.{MailboxSession, MessageIdManager}
+import org.apache.james.metrics.api.MetricFactory
+import org.reactivestreams.Publisher
+import play.api.libs.json.{JsError, JsSuccess}
+import reactor.core.scala.publisher.SMono
+import reactor.core.scheduler.Schedulers
+
+import scala.jdk.CollectionConverters._
+
+class EmailSetMethod @Inject()(serializer: EmailSetSerializer,
+ messageIdManager: MessageIdManager,
+ val metricFactory: MetricFactory,
+ val sessionSupplier: SessionSupplier) extends Method {
+
+ override val methodName: MethodName = MethodName("Email/set")
+ override val requiredCapabilities: Capabilities = Capabilities(CORE_CAPABILITY, MAIL_CAPABILITY)
+
+ override def process(capabilities: Set[CapabilityIdentifier], invocation: InvocationWithContext, mailboxSession: MailboxSession): Publisher[InvocationWithContext] = {
+ asEmailSetRequest(invocation.invocation.arguments)
+ .flatMap(request => {
+ for {
+ destroyed <- destroy(request, mailboxSession)
+ } yield {
+ InvocationWithContext(
+ invocation = Invocation(
+ methodName = invocation.invocation.methodName,
+ arguments = Arguments(serializer.serialize(EmailSetResponse(
+ accountId = request.accountId,
+ newState = State.INSTANCE,
+ destroyed = destroyed))),
+ methodCallId = invocation.invocation.methodCallId),
+ processingContext = invocation.processingContext
+ )
+ }
+ })
+ }
+
+ private def asEmailSetRequest(arguments: Arguments): SMono[EmailSetRequest] = {
+ serializer.deserialize(arguments.value) match {
+ case JsSuccess(request, _) => SMono.just(request)
+ case errors: JsError => SMono.raiseError(new IllegalArgumentException(ResponseSerializer.serialize(errors).toString))
+ }
+ }
+
+ private def destroy(emailSetRequest: EmailSetRequest, mailboxSession: MailboxSession): SMono[Option[DestroyIds]] =
+ emailSetRequest.destroy
+ .map(destroyId => deleteMessages(destroyId, mailboxSession))
+ .getOrElse(SMono.just(None))
+
+ private def deleteMessages(destroyIds: DestroyIds, mailboxSession: MailboxSession): SMono[Option[DestroyIds]] = {
+ SMono.fromCallable[DeleteResult](() => messageIdManager.delete(destroyIds.value.asJava, mailboxSession))
+ .subscribeOn(Schedulers.elastic)
+ .map(deleteResult => Some(DestroyIds(deleteResult.getDestroyed.asScala.toSeq)))
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org