Skip to content

Commit

Permalink
MAILBOX-359 Added Scala Event Serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
trantienduchn authored and chibenwa committed Dec 19, 2018
1 parent 0f9024f commit 39bc507
Show file tree
Hide file tree
Showing 11 changed files with 1,663 additions and 16 deletions.
Expand Up @@ -61,6 +61,11 @@ public FlagsBuilder add(Flags... flagsArray) {
return this;
}

public FlagsBuilder merge(FlagsBuilder flagsBuilder) {
internalFlags.add(flagsBuilder.internalFlags);
return this;
}

public Flags build() {
return new Flags(internalFlags);
}
Expand Down
Expand Up @@ -24,7 +24,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedMap;

import org.apache.james.core.User;
import org.apache.james.core.quota.QuotaCount;
Expand Down Expand Up @@ -429,7 +428,7 @@ public List<UpdatedFlags> getUpdatedFlags() {
class Added extends MetaDataHoldingEvent {
private final Map<MessageUid, MessageMetaData> added;

public Added(MailboxSession.SessionId sessionId, User user, MailboxPath path, MailboxId mailboxId, SortedMap<MessageUid, MessageMetaData> uids) {
public Added(MailboxSession.SessionId sessionId, User user, MailboxPath path, MailboxId mailboxId, Map<MessageUid, MessageMetaData> uids) {
super(sessionId, user, path, mailboxId);
this.added = ImmutableMap.copyOf(uids);
}
Expand All @@ -447,6 +446,29 @@ public MessageMetaData getMetaData(MessageUid uid) {
public Collection<MessageUid> getUids() {
return added.keySet();
}

public Map<MessageUid, MessageMetaData> getAdded() {
return added;
}

@Override
public final boolean equals(Object o) {
if (o instanceof Added) {
Added that = (Added) o;

return Objects.equals(this.sessionId, that.sessionId)
&& Objects.equals(this.user, that.user)
&& Objects.equals(this.path, that.path)
&& Objects.equals(this.mailboxId, that.mailboxId)
&& Objects.equals(this.added, that.added);
}
return false;
}

@Override
public final int hashCode() {
return Objects.hash(sessionId, user, path, mailboxId, added);
}
}

}
Expand Up @@ -43,4 +43,9 @@ void mailboxDeletionShouldMatchBeanContract() {
void mailboxACLUpdatedShouldMatchBeanContract() {
EqualsVerifier.forClass(MailboxListener.MailboxACLUpdated.class).verify();
}

@Test
void addedShouldMatchBeanContract() {
EqualsVerifier.forClass(MailboxListener.Added.class).verify();
}
}
Expand Up @@ -22,15 +22,21 @@ package org.apache.james.event.json
import java.time.Instant
import java.util.Optional

import javax.mail.{Flags => JavaMailFlags}
import julienrf.json.derived
import org.apache.james.core.quota.{QuotaCount, QuotaSize, QuotaValue}
import org.apache.james.core.{Domain, User}
import org.apache.james.event.json.DTOs.{ACLDiff, MailboxPath, Quota}
import org.apache.james.mailbox.MailboxListener.{MailboxACLUpdated => JavaMailboxACLUpdated, MailboxAdded => JavaMailboxAdded, MailboxDeletion => JavaMailboxDeletion, MailboxRenamed => JavaMailboxRenamed, QuotaUsageUpdatedEvent => JavaQuotaUsageUpdatedEvent}
import org.apache.james.event.json.MetaDataDTO.Flags
import org.apache.james.mailbox.MailboxListener.{Added => JavaAdded, MailboxACLUpdated => JavaMailboxACLUpdated,
MailboxAdded => JavaMailboxAdded, MailboxDeletion => JavaMailboxDeletion, MailboxRenamed => JavaMailboxRenamed,
QuotaUsageUpdatedEvent => JavaQuotaUsageUpdatedEvent}
import org.apache.james.mailbox.MailboxSession.SessionId
import org.apache.james.mailbox.model.{MailboxId, QuotaRoot, MailboxACL => JavaMailboxACL, Quota => JavaQuota}
import org.apache.james.mailbox.{Event => JavaEvent}
import play.api.libs.json.{JsError, JsNull, JsNumber, JsObject, JsResult, JsString, JsSuccess, Json, OFormat, Reads, Writes}
import org.apache.james.mailbox.model.{MailboxId, MessageId, QuotaRoot, MailboxACL => JavaMailboxACL, Quota => JavaQuota}
import org.apache.james.mailbox.{MessageUid, Event => JavaEvent}
import play.api.libs.json.{JsArray, JsError, JsNull, JsNumber, JsObject, JsResult, JsString, JsSuccess, Json, OFormat, Reads, Writes}

import scala.collection.JavaConverters._

private sealed trait Event {
def toJava: JavaEvent
Expand Down Expand Up @@ -60,6 +66,16 @@ private object DTO {
sizeQuota: Quota[QuotaSize], time: Instant) extends Event {
override def toJava: JavaEvent = new JavaQuotaUsageUpdatedEvent(user, quotaRoot, countQuota.toJava, sizeQuota.toJava, time)
}

case class Added(sessionId: SessionId, user: User, path: MailboxPath, mailboxId: MailboxId,
added: Map[MessageUid, MetaDataDTO.MessageMetaData]) extends Event {
override def toJava: JavaEvent = new JavaAdded(
sessionId,
user,
path.toJava,
mailboxId,
added.map(entry => entry._1 -> entry._2.toJava).asJava)
}
}

private object ScalaConverter {
Expand All @@ -85,6 +101,7 @@ private object ScalaConverter {
totalDeletedSize = event.getTotalDeletedSize,
mailboxId = event.getMailboxId)


private def toScala(event: JavaMailboxRenamed): DTO.MailboxRenamed = DTO.MailboxRenamed(
sessionId = event.getSessionId,
user = event.getUser,
Expand All @@ -99,17 +116,26 @@ private object ScalaConverter {
sizeQuota = Quota.toScala(event.getSizeQuota),
time = event.getInstant)

private def toScala(event: JavaAdded): DTO.Added = DTO.Added(
sessionId = event.getSessionId,
user = event.getUser,
path = MailboxPath.fromJava(event.getMailboxPath),
mailboxId = event.getMailboxId,
added = event.getAdded.asScala.map(entry => entry._1 -> MetaDataDTO.MessageMetaData.fromJava(entry._2)).toMap
)

def toScala(javaEvent: JavaEvent): Event = javaEvent match {
case e: JavaMailboxACLUpdated => toScala(e)
case e: JavaMailboxAdded => toScala(e)
case e: JavaMailboxDeletion => toScala(e)
case e: JavaMailboxRenamed => toScala(e)
case e: JavaQuotaUsageUpdatedEvent => toScala(e)
case _ => throw new RuntimeException("no Scala convertion known")
case e: JavaAdded => toScala(e)
case _ => throw new RuntimeException("no Scala conversion known")
}
}

private class JsonSerialize(mailboxIdFactory: MailboxId.Factory) {
private class JsonSerialize(mailboxIdFactory: MailboxId.Factory, messageIdFactory: MessageId.Factory) {
implicit val userWriters: Writes[User] = (user: User) => JsString(user.asString)
implicit val quotaRootWrites: Writes[QuotaRoot] = quotaRoot => JsString(quotaRoot.getValue)
implicit val quotaValueWrites: Writes[QuotaValue[_]] = value => if (value.isUnlimited) JsNull else JsNumber(value.asLong())
Expand All @@ -122,6 +148,10 @@ private class JsonSerialize(mailboxIdFactory: MailboxId.Factory) {
implicit val aclEntryKeyWrites: Writes[JavaMailboxACL.EntryKey] = value => JsString(value.serialize())
implicit val aclRightsWrites: Writes[JavaMailboxACL.Rfc4314Rights] = value => JsString(value.serialize())
implicit val aclDiffWrites: Writes[ACLDiff] = Json.writes[ACLDiff]
implicit val messageIdWrites: Writes[MessageId] = value => JsString(value.serialize())
implicit val messageUidWrites: Writes[MessageUid] = value => JsNumber(value.asLong())
implicit val flagsWrites: Writes[JavaMailFlags] = value => JsArray(Flags.fromJavaFlags(value).map(flag => JsString(flag)))
implicit val messageMetaDataWrites: Writes[MetaDataDTO.MessageMetaData] = Json.writes[MetaDataDTO.MessageMetaData]

implicit val aclEntryKeyReads: Reads[JavaMailboxACL.EntryKey] = {
case JsString(keyAsString) => JsSuccess(JavaMailboxACL.EntryKey.deserialize(keyAsString))
Expand Down Expand Up @@ -161,6 +191,18 @@ private class JsonSerialize(mailboxIdFactory: MailboxId.Factory) {
case JsString(userAsString) => JsSuccess(User.fromUsername(userAsString))
case _ => JsError()
}
implicit val messageIdReads: Reads[MessageId] = {
case JsString(value) => JsSuccess(messageIdFactory.fromString(value))
case _ => JsError()
}
implicit val messageUidReads: Reads[MessageUid] = {
case JsNumber(value) => JsSuccess(MessageUid.of(value.toLong))
case _ => JsError()
}
implicit val flagsReads: Reads[JavaMailFlags] = {
case JsArray(seqOfJsValues) => JsSuccess(Flags.toJavaFlags(seqOfJsValues.toArray.map(jsValue => jsValue.toString())))
case _ => JsError()
}

implicit def scopeMapReads[V](implicit vr: Reads[V]): Reads[Map[JavaQuota.Scope, V]] =
Reads.mapReads[JavaQuota.Scope, V] { str =>
Expand All @@ -172,6 +214,7 @@ private class JsonSerialize(mailboxIdFactory: MailboxId.Factory) {
JsObject(m.map { case (k, v) => (k.toString, vr.writes(v)) }.toSeq)
}


implicit def scopeMapReadsACL[V](implicit vr: Reads[V]): Reads[Map[JavaMailboxACL.EntryKey, V]] =
Reads.mapReads[JavaMailboxACL.EntryKey, V] { str =>
Json.fromJson[JavaMailboxACL.EntryKey](JsString(str))
Expand All @@ -182,10 +225,21 @@ private class JsonSerialize(mailboxIdFactory: MailboxId.Factory) {
JsObject(m.map { case (k, v) => (k.toString, vr.writes(v)) }.toSeq)
}

implicit def scopeMessageUidMapReads[V](implicit vr: Reads[V]): Reads[Map[MessageUid, V]] =
Reads.mapReads[MessageUid, V] { str =>
JsSuccess(MessageUid.of(str.toLong))
}

implicit def scopeMessageUidMapWrite[V](implicit vr: Writes[V]): Writes[Map[MessageUid, V]] =
(m: Map[MessageUid, V]) => {
JsObject(m.map { case (k, v) => (String.valueOf(k.asLong()), vr.writes(v)) }.toSeq)
}

implicit val aclDiffReads: Reads[ACLDiff] = Json.reads[ACLDiff]
implicit val mailboxPathReads: Reads[MailboxPath] = Json.reads[MailboxPath]
implicit val quotaCReads: Reads[Quota[QuotaCount]] = Json.reads[Quota[QuotaCount]]
implicit val quotaSReads: Reads[Quota[QuotaSize]] = Json.reads[Quota[QuotaSize]]
implicit val messageMetaDataReads: Reads[MetaDataDTO.MessageMetaData] = Json.reads[MetaDataDTO.MessageMetaData]

implicit val eventOFormat: OFormat[Event] = derived.oformat()

Expand All @@ -194,12 +248,13 @@ private class JsonSerialize(mailboxIdFactory: MailboxId.Factory) {
def fromJson(json: String): JsResult[Event] = Json.fromJson[Event](Json.parse(json))
}

class EventSerializer(mailboxIdFactory: MailboxId.Factory) {
def toJson(event: JavaEvent): String = new JsonSerialize(mailboxIdFactory).toJson(ScalaConverter.toScala(event))
class EventSerializer(mailboxIdFactory: MailboxId.Factory, messageIdFactory: MessageId.Factory) {
def toJson(event: JavaEvent): String = new JsonSerialize(mailboxIdFactory, messageIdFactory).toJson(ScalaConverter.toScala(event))

def fromJson(json: String): JsResult[JavaEvent] = {
new JsonSerialize(mailboxIdFactory)
new JsonSerialize(mailboxIdFactory, messageIdFactory)
.fromJson(json)
.map(event => event.toJava)
}
}

@@ -0,0 +1,90 @@
/** **************************************************************
* 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.event.json

import java.time.Instant
import java.util.Date

import javax.mail.{Flags => JavaMailFlags}
import org.apache.james.mailbox.model.{MessageId, MessageMetaData => JavaMessageMetaData}
import org.apache.james.mailbox.{FlagsBuilder, MessageUid}

object MetaDataDTO {

object MessageMetaData {
def fromJava(javaMessageMetaData: JavaMessageMetaData): MessageMetaData = MetaDataDTO.MessageMetaData(
javaMessageMetaData.getUid,
javaMessageMetaData.getModSeq,
javaMessageMetaData.getFlags,
javaMessageMetaData.getSize,
javaMessageMetaData.getInternalDate.toInstant,
javaMessageMetaData.getMessageId)
}

object Flags {
val ANSWERED = "\\Answered"
val DELETED = "\\Deleted"
val DRAFT = "\\Draft"
val FLAGGED = "\\Flagged"
val RECENT = "\\Recent"
val SEEN = "\\Seen"
val ALL_SYSTEM_FLAGS = List(ANSWERED, DELETED, DRAFT, FLAGGED, RECENT, SEEN)

def toJavaFlags(serializedFlags: Array[String]): JavaMailFlags = {
serializedFlags
.map(toFlagBuilder)
.reduceOption(_ merge _)
.getOrElse(new FlagsBuilder())
.build()
}

def toFlagBuilder(flag: String): FlagsBuilder = ALL_SYSTEM_FLAGS.contains(flag) match {
case true => new FlagsBuilder().add(stringToSystemFlag(flag))
case false => new FlagsBuilder().add(flag)
}

def fromJavaFlags(flags: JavaMailFlags): Array[String] = {
flags.getUserFlags ++ flags.getSystemFlags.map(flag => systemFlagToString(flag))
}

private def stringToSystemFlag(serializedFlag: String): JavaMailFlags.Flag = serializedFlag match {
case ANSWERED => JavaMailFlags.Flag.ANSWERED
case DELETED => JavaMailFlags.Flag.DELETED
case DRAFT => JavaMailFlags.Flag.DRAFT
case FLAGGED => JavaMailFlags.Flag.FLAGGED
case RECENT => JavaMailFlags.Flag.RECENT
case SEEN => JavaMailFlags.Flag.SEEN
case _ => throw new IllegalArgumentException(serializedFlag + " is not a system flag")
}

private def systemFlagToString(flag: JavaMailFlags.Flag): String = flag match {
case JavaMailFlags.Flag.ANSWERED => ANSWERED
case JavaMailFlags.Flag.DELETED => DELETED
case JavaMailFlags.Flag.DRAFT => DRAFT
case JavaMailFlags.Flag.FLAGGED => FLAGGED
case JavaMailFlags.Flag.RECENT => RECENT
case JavaMailFlags.Flag.SEEN => SEEN
}
}

case class MessageMetaData(uid: MessageUid, modSeq: Long, flags: JavaMailFlags, size: Long, internalDate: Instant, messageId: MessageId) {
def toJava: JavaMessageMetaData = new JavaMessageMetaData(uid, modSeq, flags, size, Date.from(internalDate), messageId)
}
}

0 comments on commit 39bc507

Please sign in to comment.