Skip to content

Instantly share code, notes, and snippets.

@berngp
Created November 21, 2011 06:10
Show Gist options
  • Save berngp/1381804 to your computer and use it in GitHub Desktop.
Save berngp/1381804 to your computer and use it in GitHub Desktop.
Lift MongoMapField with support of embedded collections.
package com.ign.lift.record
import java.util.{Calendar, Date, GregorianCalendar, UUID}
import java.util.regex.Pattern
import net.liftweb.json.JsonAST._
import net.liftweb.json.Formats
import com.mongodb.{BasicDBList, BasicDBObject, DBRef}
import scala.collection.JavaConversions._
import org.bson.types.ObjectId
private[record] object Meta {
/*
* For converting scala objects into DBObject values
*/
object Reflection {
import math.BigInt
/*
* These don't require a conversion and can be put directly into a DBObject
*/
val primitives = Set[Class[_]](
classOf[String], classOf[Int], classOf[Long], classOf[Double],
classOf[Float], classOf[Byte], classOf[BigInt], classOf[Boolean],
classOf[Short], classOf[java.lang.Integer], classOf[java.lang.Long],
classOf[java.lang.Double], classOf[java.lang.Float],
classOf[java.lang.Byte], classOf[java.lang.Boolean],
classOf[java.lang.Short])
def primitive_?(clazz: Class[_]) = primitives contains clazz
/*
* This is used to convert DBObjects into JObjects
*/
def primitive2jvalue(a: Any) = a match {
case x: String => JString(x)
case x: Int => JInt(x)
case x: Long => JInt(x)
case x: Double => JDouble(x)
case x: Float => JDouble(x)
case x: Byte => JInt(BigInt(x))
case x: BigInt => JInt(x)
case x: Boolean => JBool(x)
case x: Short => JInt(BigInt(x))
case x: java.lang.Integer => JInt(BigInt(x.asInstanceOf[Int]))
case x: java.lang.Long => JInt(BigInt(x.asInstanceOf[Long]))
case x: java.lang.Double => JDouble(x.asInstanceOf[Double])
case x: java.lang.Float => JDouble(x.asInstanceOf[Float])
case x: java.lang.Byte => JInt(BigInt(x.asInstanceOf[Byte]))
case x: java.lang.Boolean => JBool(x.asInstanceOf[Boolean])
case x: java.lang.Short => JInt(BigInt(x.asInstanceOf[Short]))
case _ => sys.error("not a primitive " + a.asInstanceOf[AnyRef].getClass)
}
/*
* Date types require formatting
*/
val datetypes = Set[Class[_]](
classOf[Calendar], classOf[Date], classOf[GregorianCalendar])
def datetype_?(clazz: Class[_]) = datetypes contains clazz
def datetype2jvalue(a: Any)(implicit formats: Formats) = a match {
case x: Calendar => dateAsJValue(x.getTime, formats)
case x: Date => dateAsJValue(x, formats)
}
def datetype2dbovalue(a: Any) = a match {
case x: Calendar => x.getTime
case x: Date => x
}
/*
* Extended Mongo types.
*/
val mongotypes = Set[Class[_]](
classOf[DBRef], classOf[ObjectId], classOf[Pattern], classOf[UUID], classOf[BasicDBList], classOf[BasicDBObject])
def mongotype_?(clazz: Class[_]) = mongotypes contains clazz
/*
* Definitive place for JValue conversion of mongo types
*/
def mongotype2jvalue(a: Any)(implicit formats: Formats): JValue = a match {
case x: ObjectId => objectIdAsJValue(x, formats)
case x: Pattern => patternAsJValue(x)
case x: UUID => uuidAsJValue(x)
case x: BasicDBList => basicDBListAsJValue(x)
case x: BasicDBObject => basicDBObjectAsJValue(x)
case x: DBRef => sys.error("DBRefs are not supported.")
case _ => sys.error("not a mongotype " + a.asInstanceOf[AnyRef].getClass)
}
/**
* Scala & Java Lists and Maps
*/
val langCollections = Set[Class[_]](
classOf[List[_]],
classOf[Map[String, _]],
classOf[java.util.List[_]],
classOf[java.util.Map[_, _]])
def langCollection_?(clazz: Class[_]) = langCollections.find {
c: Class[_] => c.isAssignableFrom(clazz)
}.isDefined
/*
* This is used to convert List and Collections to JObjects.
*/
def collection2jvalue(a: Any)(implicit formats: Formats): JValue = a match {
case x: List[_] => scalaListAsJValue(x)
case x: java.util.List[_] => scalaListAsJValue(x.toList)
case x: Map[_, _] => scalaMapAsJValue(x)
case x: java.util.Map[_, _] => scalaMapAsJValue(x)
case _ => sys.error("not an identifiable collection " + a.asInstanceOf[AnyRef].getClass)
}
def scalaListAsJValue(a: List[_])(implicit formats: Formats) = JArray(a.map(_asJValue(_, formats)))
def scalaMapAsJValue(a: scala.collection.Map[_, _])(implicit formats: Formats) = JObject(
a.map(t => JField(t._1.toString, _asJValue(t._2, formats))).toList)
}
def dateAsJValue(d: Date, formats: Formats) = JObject(JField("$dt", JString(formats.dateFormat.format(d))) :: Nil)
def objectIdAsJValue(oid: ObjectId): JValue = JObject(JField("$oid", JString(oid.toString)) :: Nil)
def patternAsJValue(p: Pattern): JValue = JObject(JField("$regex", JString(p.pattern)) :: JField("$flags", JInt(p.flags)) :: Nil)
def uuidAsJValue(u: UUID): JValue = JObject(JField("$uuid", JString(u.toString)) :: Nil)
def objectIdAsJValue(oid: ObjectId, formats: Formats): JValue =
if (isObjectIdSerializerUsed(formats))
objectIdAsJValue(oid)
else
JString(oid.toString)
def basicDBListAsJValue(a: BasicDBList)(implicit formats: Formats) = JArray(
a.iterator().toList.map(_asJValue(_, formats)))
def basicDBObjectAsJValue(a: BasicDBObject)(implicit formats: Formats) = JObject(
a.iterator.toList.map(t => JField(t._1, _asJValue(t._2, formats))))
def _asJValue(value: Any, formats: Formats): JValue = value match {
case x if Reflection.primitive_?(x.getClass) => Reflection.primitive2jvalue(x)
case x if Reflection.mongotype_?(x.getClass) => Reflection.mongotype2jvalue(x)(formats)
case x if Reflection.datetype_?(x.getClass) => Reflection.datetype2jvalue(x)(formats)
case x if Reflection.langCollection_?(x.getClass) => Reflection.collection2jvalue(x)(formats)
case _ => JNothing
}
/*
* Check to see if the ObjectIdSerializer is being used.
*/
private def isObjectIdSerializerUsed(formats: Formats): Boolean =
formats.customSerializers.exists(_.getClass == objectIdSerializerClass)
private val objectIdSerializerClass = classOf[net.liftweb.mongodb.ObjectIdSerializer]
}
package com.ign.lift.record
import net.liftweb.mongodb.record.field.MongoMapField
import net.liftweb.mongodb.record.BsonRecord
import net.liftweb.common.{Empty, Full}
import net.liftweb.json.JsonAST._
import net.liftweb.record._
class MongoMapField_V1[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: OwnerType)
extends MongoMapField[OwnerType, MapValueType](rec) {
override def setFromJValue(jvalue: JValue) = jvalue match {
case JNothing | JNull if optional_? => setBox(Empty)
case JObject(obj) => setBox(Full(
Map() ++ obj.map(jf => (jf.name, valueExtractor(jf.value)))
))
case other => setBox(FieldHelpers.expectedA("JObject", other))
}
protected def valueExtractor(value: JValue): MapValueType = (value match {
case jint: JInt => mapValueTypeExtractor(jint.values)
case jarray: JArray =>
val _list = new java.util.LinkedList[Any]()
jarray.values.foreach(_v => _list.add(mapValueTypeExtractor(_v)))
java.util.Collections.unmodifiableList(_list).asInstanceOf[MapValueType]
case jobject: JObject =>
val _map: java.util.Map[String, Any] = new java.util.LinkedHashMap()
jobject.values.foreach(t => _map.put(t._1, mapValueTypeExtractor(t._2)))
java.util.Collections.unmodifiableMap[String, Any](_map).asInstanceOf[MapValueType]
case _ => value.values.asInstanceOf[MapValueType]
}).asInstanceOf[MapValueType]
protected def mapValueTypeExtractor(value: Any): Any = (value match {
case n: BigInt => n.intValue()
case n: JInt => mapValueTypeExtractor(n.values)
case arr: JArray =>
val _list = new java.util.LinkedList[Any]()
arr.values.foreach(_v => _list.add(mapValueTypeExtractor(_v)))
java.util.Collections.unmodifiableList(_list)
case obj: JObject =>
val _map: java.util.Map[String, Any] = new java.util.LinkedHashMap()
obj.values.foreach(t => _map.put(t._1, mapValueTypeExtractor(t._2)))
java.util.Collections.unmodifiableMap[String, Any](_map)
case list: List[_] =>
val _list = new java.util.LinkedList[Any]()
list.asInstanceOf[List[Any]].foreach(t => _list.add(mapValueTypeExtractor(t)))
java.util.Collections.unmodifiableList(_list)
case map: Map[_, _] =>
val _map: java.util.Map[String, Any] = new java.util.LinkedHashMap[String, Any]()
map.asInstanceOf[Map[String, Any]].foreach(t => _map.put(t._1, mapValueTypeExtractor(t._2)))
java.util.Collections.unmodifiableMap[String, Any](_map)
case _ => value
}).asInstanceOf[MapValueType]
override def asJValue = JObject(value.keys.map {
key => JField(key, value(key).asInstanceOf[AnyRef] match {
case null => JNull
case aValue: Any => Meta._asJValue(aValue, owner.meta.formats)
})
}.toList)
}
package com.ign.lift.record
import net.liftweb.mongodb.record.BsonRecord
import net.liftweb.json.JsonAST._
import collection.immutable.TreeSet
class SortableMongoMapField[OwnerType <: BsonRecord[OwnerType], MapValueType](rec: OwnerType)
extends MongoMapField_V1[OwnerType, MapValueType](rec) {
override def asJValue = JObject( sortKeys(value.keys).map {
key => JField(key, value(key).asInstanceOf[AnyRef] match {
case null => JNull
case aValue: Any => Meta._asJValue(aValue, owner.meta.formats)
})
}.toList)
def sortKeys(keys:Iterable[String]) = (TreeSet()(Ordering.String) ++ keys)
}
@nikisix
Copy link

nikisix commented Jul 24, 2012

Hi Bernardo,
I'm using Lift to GET some json and casbah post it to a mongo db like so:
tryo(parse(request.body)) match {
...
val linkedHash = new util.LinkedHashMap [String, Any]
linkedHash.putAll(JavaConversions.mapAsJavaMap(gameJson.values))
val dbobj = new BasicDBObject(gameJson.values)
val collection = db.getCollection("buildmodel")
collection.save(dbobj)

When I try to serialize Int fields from the json, I'm getting the same BigInt exception (when trying to use casbah to save a map to the database) as you did in your thread: https://groups.google.com/forum/?fromgroups#!topic/liftweb/8dRa6cp9VEg

My reason for using Casbah btw is because I need to store embedded documents and lift doesn't support this. I.E. MongoMapField(String, String) would work, but I would need to store a MongoMapField(String, Map). And Casbah doesn't care how nested the map is that you want to store to the db.

I guess I have two questions for you:

  1. Do you solve the embedded map problem I'm talking about here: https://gist.github.com/1381804
  2. How do I reuse the overrides on this page to ameliorate the BigInt exception.

Thanks,
Nick

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment