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 f7980946ae51f2cec10b14ca909ac906e0b8eb4a
Author: Benoit Tellier <[email protected]>
AuthorDate: Fri Sep 18 17:07:07 2020 +0700

    JAMES-3377 Email/query implement minSize/maxSize FilterCondition
---
 .../contract/EmailQueryMethodContract.scala        | 261 +++++++++++++++++++--
 .../org/apache/james/jmap/mail/EmailQuery.scala    |   7 +-
 .../james/jmap/utils/search/MailboxFilter.scala    |  29 ++-
 3 files changed, 275 insertions(+), 22 deletions(-)

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/EmailQueryMethodContract.scala
 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
index 8381c45..d56c851 100644
--- 
a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
+++ 
b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala
@@ -19,6 +19,7 @@
 
 package org.apache.james.jmap.rfc8621.contract
 
+import java.io.ByteArrayOutputStream
 import java.nio.charset.StandardCharsets
 import java.time.format.DateTimeFormatter
 import java.time.{Instant, ZonedDateTime}
@@ -32,14 +33,18 @@ import io.restassured.RestAssured.{`given`, 
requestSpecification}
 import io.restassured.http.ContentType.JSON
 import javax.mail.Flags
 import net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson
+import net.javacrumbs.jsonunit.core.Option.IGNORING_ARRAY_ORDER
+import net.javacrumbs.jsonunit.core.internal.Options
 import org.apache.http.HttpStatus.SC_OK
 import org.apache.james.GuiceJamesServer
 import org.apache.james.jmap.http.UserCredential
 import org.apache.james.jmap.model.UTCDate
 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.inbox
 import org.apache.james.mailbox.model.{MailboxPath, MessageId}
 import org.apache.james.mime4j.dom.Message
+import org.apache.james.mime4j.message.DefaultMessageWriter
 import org.apache.james.modules.MailboxProbeImpl
 import org.apache.james.utils.DataProbeImpl
 import org.awaitility.Awaitility
@@ -154,11 +159,11 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(ANDRE, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
       .getMessageId
     server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(ANDRE.asString, otherMailboxPath, 
AppendCommand.from(message))
@@ -216,7 +221,7 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val requestDateMessage1 = 
Date.from(ZonedDateTime.now().minusDays(1).toInstant)
@@ -265,11 +270,11 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
       .getMessageId
     server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
@@ -548,11 +553,11 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     val otherMailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
       .getMessageId
     val messageId2: MessageId = server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
@@ -649,11 +654,11 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     val otherMailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
       .getMessageId
     server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
@@ -708,17 +713,243 @@ trait EmailQueryMethodContract {
   }
 
   @Test
+  def minSizeShouldBeInclusive(server: GuiceJamesServer): Unit = {
+    val message1: Message = simpleMessage("short")
+    val size1: Int = computeSize(message1)
+    // One char more than message1
+    val message2: Message = simpleMessage("short!")
+    val size2: Int = computeSize(message2)
+    // One char more than message2
+    val message3: Message = simpleMessage("short!!")
+    val size3: Int = computeSize(message3)
+
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    mailboxProbe.createMailbox(inbox(BOB))
+    val id1 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), 
AppendCommand.from(message1)).getMessageId
+    val id2 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), 
AppendCommand.from(message2)).getMessageId
+    val id3 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), 
AppendCommand.from(message3)).getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "minSize": $size2
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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)
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .inPath("$.methodResponses[0][1].ids")
+        .isEqualTo(
+          s"""[
+             |  "${id2.serialize()}",
+             |  "${id3.serialize()}"
+             |]""".stripMargin)
+    }
+  }
+
+  @Test
+  def maxSizeShouldBeExclusive(server: GuiceJamesServer): Unit = {
+    val message1: Message = simpleMessage("looooooooooooooong")
+    val size1: Int = computeSize(message1)
+    // One char more than message3
+    val message2: Message = simpleMessage("looooooooooooooong!")
+    val size2: Int = computeSize(message2)
+    // One char more than message4
+    val message3: Message = simpleMessage("looooooooooooooong!!")
+    val size3: Int = computeSize(message3)
+
+    val mailboxProbe = server.getProbe(classOf[MailboxProbeImpl])
+    mailboxProbe.createMailbox(inbox(BOB))
+    val id1 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), 
AppendCommand.from(message1)).getMessageId
+    val id2 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), 
AppendCommand.from(message2)).getMessageId
+    val id3 = mailboxProbe.appendMessage(BOB.asString, inbox(BOB), 
AppendCommand.from(message3)).getMessageId
+
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "maxSize": $size2
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      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)
+        .withOptions(new Options(IGNORING_ARRAY_ORDER))
+        .inPath("$.methodResponses[0][1].ids")
+        .isEqualTo(
+          s"""[
+             |  "${id1.serialize()}"
+             |]""".stripMargin)
+    }
+  }
+
+  @Test
+  def maxSizeShouldRejectNegative(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "maxSize": -1
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post.prettyPeek()
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "75128aab4b1b",
+             |    "methodResponses": [
+             |        [
+             |            "error",
+             |            {
+             |                "type": "invalidArguments",
+             |                "description": 
"{\\"errors\\":[{\\"path\\":\\"obj.filter.maxSize\\",\\"messages\\":[\\"Predicate
 (-1 < 0) did not fail.\\"]}]}"
+             |            },
+             |            "c1"
+             |        ]
+             |    ]
+             |}""".stripMargin)
+    }
+  }
+
+  @Test
+  def minSizeShouldRejectNegative(): Unit = {
+    val request =
+      s"""{
+         |  "using": [
+         |    "urn:ietf:params:jmap:core",
+         |    "urn:ietf:params:jmap:mail"],
+         |  "methodCalls": [[
+         |    "Email/query",
+         |    {
+         |      "accountId": 
"29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
+         |      "filter": {
+         |        "minSize": -1
+         |      }
+         |    },
+         |    "c1"]]
+         |}""".stripMargin
+
+    awaitAtMostTenSeconds.untilAsserted { () =>
+      val response = `given`
+        .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
+        .body(request)
+      .when
+        .post.prettyPeek()
+      .`then`
+        .statusCode(SC_OK)
+        .contentType(JSON)
+        .extract
+        .body
+        .asString
+
+      assertThatJson(response)
+        .isEqualTo(
+          s"""{
+             |    "sessionState": "75128aab4b1b",
+             |    "methodResponses": [
+             |        [
+             |            "error",
+             |            {
+             |                "type": "invalidArguments",
+             |                "description": 
"{\\"errors\\":[{\\"path\\":\\"obj.filter.minSize\\",\\"messages\\":[\\"Predicate
 (-1 < 0) did not fail.\\"]}]}"
+             |            },
+             |            "c1"
+             |        ]
+             |    ]
+             |}""".stripMargin)
+    }
+  }
+
+  private def simpleMessage(message: String) = {
+    Message.Builder
+      .of
+      .setSubject("test")
+      .setBody(message, StandardCharsets.UTF_8)
+      .build
+  }
+
+  private def computeSize(message: Message): Int = {
+    val writer = new DefaultMessageWriter()
+    val stream = new ByteArrayOutputStream()
+    writer.writeMessage(message, stream)
+    stream.toByteArray.length
+  }
+
+  @Test
   def shouldListMailsNotInASpecificUserMailboxes(server: GuiceJamesServer): 
Unit = {
     val message: Message = Message.Builder
       .of
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     val otherMailboxId = 
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val messageId1: MessageId = server.getProbe(classOf[MailboxProbeImpl])
-      .appendMessage(BOB.asString, MailboxPath.inbox(BOB), 
AppendCommand.from(message))
+      .appendMessage(BOB.asString, inbox(BOB), AppendCommand.from(message))
       .getMessageId
     server.getProbe(classOf[MailboxProbeImpl])
       .appendMessage(BOB.asString, otherMailboxPath, 
AppendCommand.from(message))
@@ -766,7 +997,7 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val otherMailboxPath = MailboxPath.forUser(BOB, "other")
     server.getProbe(classOf[MailboxProbeImpl]).createMailbox(otherMailboxPath)
     val requestDateMessage1 = 
Date.from(ZonedDateTime.now().minusDays(1).toInstant)
@@ -923,7 +1154,7 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val requestDate = ZonedDateTime.now().minusDays(1)
     val messageId1 = sendMessageToBobInbox(server, message, 
Date.from(requestDate.toInstant))
 
@@ -975,7 +1206,7 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val requestDate = ZonedDateTime.now().minusDays(1)
     val messageId1 = sendMessageToBobInbox(server, message, 
Date.from(requestDate.toInstant))
 
@@ -1027,7 +1258,7 @@ trait EmailQueryMethodContract {
       .setSubject("test")
       .setBody("testmail", StandardCharsets.UTF_8)
       .build
-    
server.getProbe(classOf[MailboxProbeImpl]).createMailbox(MailboxPath.inbox(BOB))
+    server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB))
     val receivedDateMessage1 = ZonedDateTime.now().minusDays(1)
     sendMessageToBobInbox(server, message, 
Date.from(receivedDateMessage1.toInstant))
 
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
index 8f23532..db196bc 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailQuery.scala
@@ -18,9 +18,10 @@
  ****************************************************************/
 
 package org.apache.james.jmap.mail
-import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, 
LimitUnparsed, Position, QueryState, UTCDate}
+import org.apache.james.jmap.mail.Email.Size
 import org.apache.james.jmap.mail.IsAscending.{ASCENDING, DESCENDING}
 import org.apache.james.jmap.model.Limit.Limit
+import org.apache.james.jmap.model.{AccountId, CanCalculateChanges, Keyword, 
LimitUnparsed, Position, QueryState, UTCDate}
 import org.apache.james.mailbox.model.SearchQuery.Sort.Order.{NATURAL, REVERSE}
 import org.apache.james.mailbox.model.SearchQuery.Sort.SortClause
 import org.apache.james.mailbox.model.{MailboxId, MessageId, SearchQuery}
@@ -30,7 +31,9 @@ case class FilterCondition(inMailbox: Option[MailboxId],
                            before: Option[UTCDate],
                            after: Option[UTCDate],
                            hasKeyword: Option[Keyword],
-                           notKeyword: Option[Keyword])
+                           notKeyword: Option[Keyword],
+                           minSize: Option[Size],
+                           maxSize: Option[Size])
 
 case class EmailQueryRequest(accountId: AccountId, limit: 
Option[LimitUnparsed], filter: Option[FilterCondition], comparator: 
Option[Set[Comparator]])
 
diff --git 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
index c221cdd..91be2f8 100644
--- 
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
+++ 
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/utils/search/MailboxFilter.scala
@@ -20,9 +20,8 @@ package org.apache.james.jmap.utils.search
 
 import java.util.Date
 
-import javax.mail.Flags
 import org.apache.james.jmap.mail.EmailQueryRequest
-import org.apache.james.mailbox.model.SearchQuery.{Conjunction, 
ConjunctionCriterion, Criterion, DateComparator, DateOperator, DateResolution, 
FlagCriterion, InternalDateCriterion}
+import org.apache.james.mailbox.model.SearchQuery.{Conjunction, 
ConjunctionCriterion, Criterion, DateComparator, DateOperator, DateResolution, 
InternalDateCriterion}
 import org.apache.james.mailbox.model.{MultimailboxesSearchQuery, SearchQuery}
 
 import scala.jdk.CollectionConverters._
@@ -61,7 +60,8 @@ object MailboxFilter {
 
   object QueryFilter {
     def buildQuery(request: EmailQueryRequest): SearchQuery.Builder = {
-      List(ReceivedBefore, ReceivedAfter, HasKeyWord, NotKeyWord).foldLeft(new 
SearchQuery.Builder())((builder, filter) => filter.toQuery(builder, request))
+      List(ReceivedBefore, ReceivedAfter, HasKeyWord, NotKeyWord, MinSize, 
MaxSize)
+        .foldLeft(SearchQuery.builder())((builder, filter) => 
filter.toQuery(builder, request))
     }
   }
 
@@ -77,11 +77,30 @@ object MailboxFilter {
   }
   case object ReceivedAfter extends QueryFilter {
     override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =  request.filter.flatMap(_.after) 
match {
-      case Some(after) => {
+      case Some(after) =>
         val strictlyAfter = new InternalDateCriterion(new 
DateOperator(DateComparator.AFTER, Date.from(after.asUTC.toInstant), 
DateResolution.Second))
         builder
           .andCriteria(strictlyAfter)
-      }
+      case None => builder
+    }
+  }
+
+  case object MinSize extends QueryFilter {
+    override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =  request.filter.flatMap(_.minSize) 
match {
+      case Some(minSize) =>
+        builder
+          .andCriteria(SearchQuery.or(
+            SearchQuery.sizeGreaterThan(minSize.value),
+            SearchQuery.sizeEquals(minSize.value)))
+      case None => builder
+    }
+  }
+
+  case object MaxSize extends QueryFilter {
+    override def toQuery(builder: SearchQuery.Builder, request: 
EmailQueryRequest): SearchQuery.Builder =  request.filter.flatMap(_.maxSize) 
match {
+      case Some(maxSize) =>
+        builder
+          .andCriteria(SearchQuery.sizeLessThan(maxSize.value))
       case None => builder
     }
   }


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

Reply via email to