Permalink
Browse files

Initial support for custom transformers. One hand, one million dollar…

…s, no tears.
  • Loading branch information...
1 parent 8f16bf1 commit 525d27bef9467e23f50c2961036c398f7763fd78 @rktoomey rktoomey committed Feb 15, 2013
@@ -36,6 +36,7 @@ import com.mongodb.casbah.Imports._
import java.util.concurrent.ConcurrentHashMap
import com.novus.salat.json.JSONConfig
import org.json4s.JsonAST.JObject
+import com.novus.salat.transformers.CustomTransformer
trait Context extends ContextLifecycle with Logging {
@@ -54,6 +55,8 @@ trait Context extends ContextLifecycle with Logging {
/**Per class key overrides - map key is (clazz.getName, field name) */
private[salat] val perClassKeyOverrides: ConcurrentMap[(String, String), String] = JConcurrentMapWrapper(new ConcurrentHashMap[(String, String), String]())
+ private[salat] val customTransformers: ConcurrentMap[String, CustomTransformer[_ <: AnyRef, _ <: AnyRef]] = JConcurrentMapWrapper(new ConcurrentHashMap[String, CustomTransformer[_ <: AnyRef, _ <: AnyRef]]())
+
val typeHintStrategy: TypeHintStrategy = StringTypeHintStrategy(when = TypeHintFrequency.WhenNecessary, typeHint = TypeHint)
/**Enum handling strategy is defined at the context-level, but can be overridden at the individual enum level */
@@ -93,14 +96,22 @@ trait Context extends ContextLifecycle with Logging {
caseObjectOverrides += caseObjectClazz.getName -> serializedValue
resolveCaseObjectOverrides += (parentClazz.getName, serializedValue) -> caseObjectClazz.getName
caseObjectHierarchy += parentClazz.getName
- log.info("registerCaseObjectOverride[%s]: %s <- %s will serialize as '%s'", name, parentClazz.getSimpleName, caseObjectClazz.getSimpleName, serializedValue)
+ log.debug("registerCaseObjectOverride[%s]: %s <- %s will serialize as '%s'", name, parentClazz.getSimpleName, caseObjectClazz.getSimpleName, serializedValue)
}
def registerClassLoader(cl: ClassLoader) {
classLoaders = cl +: classLoaders
log.info("registerClassLoader: ctx='%s' registering classloader='%s' (total: %d)", name, cl.toString, classLoaders.size)
}
+ def registerCustomTransformer[A <: AnyRef: Manifest, B <: AnyRef: Manifest](custom: CustomTransformer[A, B]) {
+ if (customTransformers.contains(custom.path)) {
+ sys.error("Context '%s' already contains a custom transformer for class='%s'!".format(name, custom.path))
+ }
+ customTransformers += custom.path -> custom
+ log.debug("registerCustomTransformer: %s <-> %s", manifest[A].erasure.getName, manifest[B].erasure.getName)
+ }
+
def determineFieldName(clazz: Class[_], field: SField): String = determineFieldName(clazz, field.name)
def determineFieldName(clazz: Class[_], fieldName: String): String = {
@@ -40,8 +40,11 @@ object Field {
val n = method.annotation[Key].map(_.value)
if (n.isDefined) n.get else name
}
- val _in = in.select(t, method.annotated_?[Salat])
- val _out = out.select(t, method.annotated_?[Salat])
+
+ val customTransformer = ctx.customTransformers.get(t.symbol.path)
+
+ val _in = if (customTransformer.isDefined) new CustomSerializer(customTransformer.get, t.symbol.path, t, ctx) else in.select(t, method.annotated_?[Salat])
+ val _out = if (customTransformer.isDefined) new CustomDeserializer(customTransformer.get, t.symbol.path, t, ctx) else out.select(t, method.annotated_?[Salat])
val ignore = method.annotation[Ignore].isDefined
new Field(idx = idx,
@@ -62,18 +65,21 @@ sealed abstract class Field(val idx: Int,
def in_!(value: Any) = {
val xformed = in.transform_!(value)
- // log.debug(
- // """
- // |IN:
- // | name: %s
- // | typeRefType:
- // |%s
- // | in:
- // |%s
- // | value: %s
- // | transformed: %s
- // |
- // """.stripMargin, name, typeRefType, in.getClass.getInterfaces.mkString("\n"), value, xformed)
+// log.debug(
+// """
+// |IN:
+// | name: %s
+// | typeRefType:
+// |%s
+// | in:
+// |%s
+// | value: %s
+// | transformed: %s
+// |
+// """.stripMargin, name, typeRefType, in match {
+// case c: UseCustomTransformer[_, _] => c.toString
+// case _ => in.getClass.getInterfaces.mkString("\n")
+// }, value, xformed)
xformed
}
@@ -90,7 +96,10 @@ sealed abstract class Field(val idx: Int,
// | value: %s
// | transformed: %s
// |
- // """.stripMargin, name, typeRefType, in.getClass.getInterfaces.mkString("\n"), value, xformed)
+ // """.stripMargin, name, typeRefType, out match {
+ // case c: UseCustomTransformer[_, _] => c.toString
+ // case _ => out.getClass.getInterfaces.mkString("\n")
+ // }, value, xformed)
xformed
}
@@ -0,0 +1,21 @@
+package com.novus.salat.transformers
+
+abstract class CustomTransformer[ModelObject <: AnyRef: Manifest, SerializedRepr <: AnyRef: Manifest]() {
+
+ final def in(value: Any): Any = value match {
+ case o: SerializedRepr => deserialize(o)
+ case _ => None
+ }
+
+ final def out(value: Any): Option[SerializedRepr] = value match {
+ case i: ModelObject => Option(serialize(i))
+ }
+
+ def path = manifest[ModelObject].erasure.getName
+
+ def deserialize(b: SerializedRepr): ModelObject
+
+ def serialize(a: ModelObject): SerializedRepr
+
+ override def toString = "CustomTransformer[ %s <-> %s ]".format(manifest[ModelObject].erasure.getName, manifest[SerializedRepr].erasure.getName)
+}
@@ -87,3 +87,15 @@ trait InContextTransformer {
val grater: Option[Grater[_ <: AnyRef]]
}
+abstract class UseCustomTransformer[A <: AnyRef, B <: AnyRef](val custom: CustomTransformer[A, B], override val path: String, override val t: TypeRefType, override val ctx: Context) extends Transformer(path, t)(ctx) {
+ override def toString = "UseCustomTransformer: %s".format(custom.toString)
+}
+
+class CustomSerializer[A <: AnyRef, B <: AnyRef](override val custom: CustomTransformer[A, B], override val path: String, override val t: TypeRefType, override val ctx: Context) extends UseCustomTransformer(custom, path, t, ctx) {
+ override def transform(value: Any)(implicit ctx: Context) = custom.in(value)
+}
+
+class CustomDeserializer[A <: AnyRef, B <: AnyRef](override val custom: CustomTransformer[A, B], override val path: String, override val t: TypeRefType, override val ctx: Context) extends UseCustomTransformer(custom, path, t, ctx) {
+ override def transform(value: Any)(implicit ctx: Context) = custom.out(value)
+}
+
@@ -0,0 +1,26 @@
+package com.novus.salat.test
+
+import com.mongodb.casbah.Imports._
+import com.novus.salat._
+import com.novus.salat.test.custom._
+
+class CustomTransformerSpec extends SalatSpec {
+
+ "Salat" should {
+ "allow custom transformers" in {
+ val _id = new ObjectId
+ val bar = Bar("b")
+ val baz = Baz(1, 3.14)
+ val foo = Foo(_id, bar, baz)
+ val dbo = grater[Foo].asDBObject(foo)
+ dbo should haveEntry("_t", foo.getClass.getName)
+ dbo should haveEntry("bar", "b")
+ dbo should haveEntry("baz._t", baz.getClass.getName)
+ dbo should haveEntry("baz.a", 1)
+ dbo should haveEntry("baz.b", 3.14)
+ val foo_* = grater[Foo].asObject(dbo)
+ foo_* must_== foo
+ }
+ }
+
+}
@@ -0,0 +1,21 @@
+package com.novus.salat.test.custom
+
+import com.novus.salat.transformers.CustomTransformer
+import org.bson.types.ObjectId
+import com.novus.salat.dao.SalatDAO
+import com.mongodb.casbah.MongoConnection
+import com.novus.salat.test._
+
+case class Bar(x: String)
+
+object BarTransformer extends CustomTransformer[Bar, String] {
+ def deserialize(b: String) = Bar(b)
+
+ def serialize(a: Bar) = a.x
+}
+
+case class Baz(a: Int, b: Double)
+
+case class Foo(_id: ObjectId, bar: Bar, baz: Baz)
+
+object FooDAO extends SalatDAO[Foo, ObjectId](MongoConnection()(SalatSpecDb)(custom.ctx.name))
@@ -0,0 +1,13 @@
+package com.novus.salat.test
+
+import com.novus.salat.{ TypeHintFrequency, StringTypeHintStrategy, Context }
+
+package object custom {
+ implicit val ctx = new Context() {
+ val name = "custom_transformer_spec"
+ override val typeHintStrategy = StringTypeHintStrategy(TypeHintFrequency.Always, "_t")
+ registerCustomTransformer(BarTransformer)
+ }
+
+}
+

0 comments on commit 525d27b

Please sign in to comment.