Skip to content

Commit

Permalink
Enhance EventAdapterFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
rodrigogdea committed Apr 7, 2020
1 parent 69625de commit e3f3561
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 72 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ And then you have to register the new Adapter:

serializer.addEventAdapter(new UserAddedEventAdapter)
```
### EventAdapter Factory
## EventAdapter Factory
To avoid writing boilerplate code creating Event Adapters, we can use the `EventAdapterFactory`:
```scala
case class ProductId(id: String) extends AnyVal
Expand All @@ -75,11 +75,15 @@ ReactiveMongoEventSerializer(ActorSystem()).addEventAdapter(eventAdapter)
```
It is also possible to override mappings or add unsupported mappings. All added mappings must extends from `BSONReader[_]` or `BSONWriter[_]` or both.
```scala
val reader = new BSONReader[Type1] {...}
val writer = new BSONWriter[Type2] {...}
val readerAndWriter = new BSONReader[Type3] with BSONWriter[Type3] {...}
implicit val reader = new BSONReader[Type1] {...}
implicit val writer = new BSONWriter[Type2] {...}
implicit val readerAndWriter = new BSONReader[Type3] with BSONWriter[Type3] {...}

val eventAdapter = EventAdapterFactory.adapt[Type4](withManifest = "SomeEvent", reader, writer, readerAndWriter)
val eventAdapter = EventAdapterFactory.adapt[Type4](withManifest = "SomeEvent")
```
You can also add tags asociated to the Event:
```scala
val eventAdapter = EventAdapterFactory.adapt[Type4](withManifest = "SomeEvent", Set("Tag_1", "Tag_2"))
```

## Persistence Id
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ lazy val rxmongoVersion = "0.19.5"
lazy val commonSettings = Seq(
name := "akka-reactivemongo-plugin",
organization := "null-vector",
version := "1.3.5",
version := "1.3.6",
scalaVersion := scala213,
crossScalaVersions := supportedScalaVersions,
scalacOptions := Seq(
Expand Down
27 changes: 19 additions & 8 deletions core/src/test/scala/org/nullvector/EventAdapterFactorySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,31 @@ class EventAdapterFactorySpec extends FlatSpec {

it should "override Reader Mapping" in {
val kMapping = Macros.handler[K]
val kReader: BSONDocumentReader[K] = kMapping.beforeRead({
implicit val kReader: BSONDocumentReader[K] = kMapping.beforeRead({
case BSONDocument(_) => BSONDocument("s" -> "Reader Overrided")
}: PartialFunction[BSONDocument, BSONDocument])

val eventAdapter = EventAdatpterFactory.adapt[I]("Ied", kReader)
val eventAdapter = EventAdatpterFactory.adapt[I]("Ied")
val anInstance = I(K("k"))
val document = eventAdapter.payloadToBson(anInstance)
eventAdapter.bsonToPayload(document).k.s shouldBe "Reader Overrided"
}

it should "override Writer Mapping" in {
val kMapping = Macros.handler[K]
val kWriter: BSONWriter[K] = kMapping.afterWrite({
implicit val kWriter: BSONWriter[K] = kMapping.afterWrite({
case BSONDocument(_) => BSONDocument("s" -> "Writer Overrided")
}: PartialFunction[BSONDocument, BSONDocument])

val eventAdapter = EventAdatpterFactory.adapt[I]("Ied", kWriter)
val justForTestTags: Any => Set[String] = {
case "A" => Set("TagA")
case _ => Set("TagN")
}

val eventAdapter = EventAdatpterFactory.adapt[I]("Ied", justForTestTags)

eventAdapter.tags("A") should contain ("TagA")
eventAdapter.tags("x") should contain ("TagN")
val anInstance = I(K("k"))
eventAdapter.payloadToBson(anInstance)
.getAsOpt[BSONDocument]("k").get
Expand All @@ -61,20 +69,23 @@ class EventAdapterFactorySpec extends FlatSpec {

it should "add unsupported Mapping" in {

val writer: BSONWriter[Map[Day, String]] = (t: Map[Day, String]) => Success(BSONDocument(t.map(e => e._1.toString -> BSONString(e._2))))
val reader: BSONReader[Map[Day, String]] = _.asTry[BSONDocument].map(_.toMap.map(e => Day(e._1) -> e._2.asOpt[String].get))
implicit val writer: BSONWriter[Map[Day, String]] = (t: Map[Day, String]) => Success(BSONDocument(t.map(e => e._1.toString -> BSONString("Value_" + e._2))))
implicit val reader: BSONReader[Map[Day, String]] = _.asTry[BSONDocument].map(_.toMap.map(e => Day(e._1) -> e._2.asOpt[String].get))

val dayMapping = new BSONReader[Day] with BSONWriter[Day] {
implicit val dayMapping = new BSONReader[Day] with BSONWriter[Day] {
override def readTry(bson: BSONValue): Try[Day] = bson.asTry[String].map(Day(_))

override def writeTry(t: Day): Try[BSONValue] = Success(BSONString(t.toString))
}

val eventAdapter = EventAdatpterFactory.adapt[L]("Led", writer, reader, dayMapping)
val tags = Set("aTag")
val eventAdapter = EventAdatpterFactory.adapt[L]("Led", tags)
val document = eventAdapter.payloadToBson(L(Map(Monday -> "A"), Sunday))
val payload = eventAdapter.bsonToPayload(document)

eventAdapter.tags(payload) should contain("aTag")
payload.day shouldBe Sunday
payload.m.head._2 shouldBe "Value_A"
}

}
Expand Down
89 changes: 35 additions & 54 deletions macros/src/main/scala/org/nullvector/EventAdapterMacroFactory.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,33 @@ private object EventAdapterMacroFactory {
"scala.collection.immutable.Map",
)

def adapt[E](context: blackbox.Context)
(withManifest: context.Expr[String], overrideMappings: context.Expr[Any]*)
def adaptWithTags[E](context: blackbox.Context)(withManifest: context.Expr[String], tags: context.Expr[Set[String]])
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
import context.universe._
buildAdapterExpression(context)(withManifest, q"override def tags(payload: Any) = $tags")
}

def adaptWithPayload2Tags[E](context: blackbox.Context)(withManifest: context.Expr[String], tags: context.Expr[Any => Set[String]])
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
import context.universe._
buildAdapterExpression(context)(withManifest, q"override def tags(payload: Any) = $tags(payload)")
}

def adapt[E](context: blackbox.Context)(withManifest: context.Expr[String])
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {
buildAdapterExpression(context)(withManifest, context.universe.EmptyTree)
}

private def buildAdapterExpression[E](context: blackbox.Context)
(withManifest: context.Expr[String],
tags: context.universe.Tree
)
(implicit eventTypeTag: context.WeakTypeTag[E]): context.Expr[EventAdapter[E]] = {

import context.universe._
val eventType = eventTypeTag.tpe
val eventAdapterTypeName = TypeName(eventType.toString + "EventAdapter")
val handlers: Seq[context.Tree] = implicitMappingsFor(context)(eventType, overrideMappings)
val handlers: Seq[context.Tree] = implicitMappingsFor(context)(eventType)
val code =
q"""
import reactivemongo.api.bson._
Expand All @@ -35,71 +54,33 @@ private object EventAdapterMacroFactory {
..$handlers
override def payloadToBson(payload: $eventType): BSONDocument = BSON.writeDocument(payload).get
override def bsonToPayload(doc: BSONDocument): $eventType = BSON.readDocument[$eventType](doc).get
$tags
}
new $eventAdapterTypeName
"""
//println(code)
context.Expr[EventAdapter[E]](code)
}

private def implicitMappingsFor(context: blackbox.Context)
(eventType: context.universe.Type,
overrideMappings: Seq[context.Expr[Any]]
): List[context.universe.Tree] = {
import context.universe._
validateMappings(context)(overrideMappings)
val caseClassTypes = extractCaseTypes(context)(eventType).toList.reverse.distinct
val (overridesMap, nonOverrides) = overrideMappings
// partitionMap is not implementend in scala 2.12
.map(expr => expr.actualType.typeArgs.intersect(caseClassTypes) match {
case Nil => Right(expr)
case ::(head, _) => Left(head -> expr)
})
.partition(_.isLeft)
.map((a, b) => a.map(_.left.get).groupBy(_._1) -> b.map(_.right.get)) // using left and rigth for scala 2.12 compatibility

nonOverrides.map(expr =>
ValDef(Modifiers(Flag.IMPLICIT | Flag.PRIVATE), TermName(context.freshName()), TypeTree(expr.actualType), expr.tree)
).toList :::
caseClassTypes.flatMap { caseType =>
overridesMap.get(caseType) match {
case Some(overrides) => buildImplicitDeclarations(context)(TypeTree(caseType), overrides.map(_._2).toList)
case None => buildImplicitDeclarations(context)(TypeTree(caseType), Nil)
}
}
}

private def validateMappings(context: blackbox.Context)
(overrides: Seq[context.Expr[Any]]): Unit = {
import context.universe._
val bsonWrtterType = context.typeOf[BSONWriter[_]]
val bsonReaderType = context.typeOf[BSONReader[_]]

overrides.foreach(expr =>
if (!(expr.actualType <:< typeOf[BSONReader[_]] || expr.actualType <:< typeOf[BSONWriter[_]])) {
context.abort(context.enclosingPosition,
s""" Type ${expr.actualType} in override mapping list is no valid.
|Must extends from ${typeOf[BSONReader[_]]} or ${typeOf[BSONWriter[_]]} or both.""".stripMargin)
}
)
}

private def buildImplicitDeclarations(context: blackbox.Context)
(
caseType: context.universe.TypeTree,
overrides: List[context.Expr[Any]]
): List[context.universe.Tree] = {
import context.universe._
val caseClassTypes = extractCaseTypes(context)(eventType).toList.reverse.distinct

val valDefs = overrides.map(expr =>
ValDef(Modifiers(Flag.IMPLICIT | Flag.PRIVATE), TermName(context.freshName()), TypeTree(expr.actualType), expr.tree))
val implicitHandler = overrides match {
case Nil => List(q" private implicit val ${TermName(context.freshName())}: BSONDocumentHandler[$caseType] = Macros.handler[$caseType]")
case ::(overr, Nil) if (overr.actualType <:< typeOf[BSONReader[_]] && overr.actualType <:< typeOf[BSONWriter[_]]) => Nil
case ::(overr, Nil) if overr.actualType <:< typeOf[BSONReader[_]] =>
List(q" private implicit val ${TermName(context.freshName())}: BSONWriter[$caseType] = Macros.handler[$caseType]")
case ::(overr, Nil) if overr.actualType <:< typeOf[BSONWriter[_]] =>
List(q" private implicit val ${TermName(context.freshName())}: BSONReader[$caseType] = Macros.handler[$caseType]")
case _ => Nil
caseClassTypes.collect {
case caseType if context.inferImplicitValue(appliedType(bsonWrtterType, caseType)).isEmpty &&
context.inferImplicitValue(appliedType(bsonReaderType, caseType)).isEmpty =>
q" private implicit val ${TermName(context.freshName())}: BSONDocumentHandler[$caseType] = Macros.handler[$caseType]"
case caseType if !context.inferImplicitValue(appliedType(bsonReaderType, caseType)).isEmpty =>
q" private implicit val ${TermName(context.freshName())}: BSONWriter[$caseType] = Macros.handler[$caseType]"
case caseType if !context.inferImplicitValue(appliedType(bsonWrtterType, caseType)).isEmpty =>
q" private implicit val ${TermName(context.freshName())}: BSONReader[$caseType] = Macros.handler[$caseType]"
}
implicitHandler ::: valDefs
}

private def extractCaseTypes(context: blackbox.Context)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package org.nullvector

import scala.annotation.tailrec
import scala.reflect.macros.{blackbox, whitebox}

object EventAdatpterFactory {

def adapt[E](withManifest: String, overrideMappings: Any*): EventAdapter[E] = macro EventAdapterMacroFactory.adapt[E]
def adapt[E](withManifest: String): EventAdapter[E] = macro EventAdapterMacroFactory.adapt[E]

def adapt[E](withManifest: String, tags: Any => Set[String]): EventAdapter[E] = macro EventAdapterMacroFactory.adaptWithPayload2Tags[E]

def adapt[E](withManifest: String, tags: Set[String]): EventAdapter[E] = macro EventAdapterMacroFactory.adaptWithTags[E]

}

0 comments on commit e3f3561

Please sign in to comment.