Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

- Some major tweaks to avoid the race conditions and serialization is…

…sues we've been encountering

    * Monkey Patching package-only methods in org.bson.BSON via a package-space invading helper.
        + Can now specifically add and remove individual transformers, which lets us unregister DateTime as needed.
        + GridFS now loans itself a temporary unserialization. this WILL cause a race condition in threaded environs using
            both GridFS and JodaTime deserialization so be careful.  Also - it may interfere w/ 3rd party jdk date
            deserialization hooks with it's current lazy implementation.
    * MongoConnection's constructor now loads the 'base' (non Joda) serializers automatically.  TODO - Ability to turn this off.
- To avoid an occasional toString race condition, added an 'always patch' functionality to the queryo perators to load any needed transformers if they aren't there.
  • Loading branch information...
commit ee3520bd5fd5a4c8a505e6a8b4cadddfaa7d5648 1 parent 860e425
@bwmcadams bwmcadams authored
View
4 project/build.properties
@@ -1,9 +1,9 @@
#Project properties
-#Thu Apr 22 13:15:34 EDT 2010
+#Sat Jul 03 18:46:36 EDT 2010
project.organization=com.novus
project.name=casbah
sbt.version=0.7.4
-project.version=1.0-RC3
+project.version=1.0.0-RC4
def.scala.version=2.7.7
build.scala.versions=2.8.0.RC6
project.initialize=false
View
39 src/main/scala/mongodb/GridFS.scala
@@ -37,6 +37,8 @@ import java.io._
import scala.reflect._
import scalaj.collection.Imports._
+import org.scala_tools.time.Imports._
+
// todo - look into potential naming conflicts...
/**
@@ -177,15 +179,36 @@ class GridFS protected[mongodb](val underlying: MongoGridFS) extends Iterable[Gr
def createFile(in: InputStream, filename: String): GridFSInputFile = underlying.createFile(in, filename)
def withNewFile(in: InputStream, filename: String)(op: FileWriteOp) { loan(createFile(in, filename))({ fh => op(fh); fh.save }) }
+ /** Hacky fun -
+ * Unload the Joda Time code, IF ITS LOADED, and reload it after we're done.
+ * This should minimize conflicts or exceptions from forgetting.
+ * This is hacky as it can potentially clobber anybody's "custom" java.util.Date deserializer with ours.
+ * TODO - Make this more elegant
+ */
+ def sansJodaTime[T](op: => T) = org.bson.BSONDecoders(classOf[java.util.Date]) match {
+ case Some(transformer) => {
+ log.info("DateTime Decoder was loaded; unloading before continuing.")
+ new conversions.scala.JodaDateTimeDeserializer { unregister() }
+ val ret = op
+ log.info("Retrieval finished. Re-registering decoder.")
+ new conversions.scala.JodaDateTimeDeserializer { register() }
+ ret
+ }
+ case None => {
+ log.info("Didn't find a registration for JodaTime: %s", org.bson.BSONDecoders())
+ op
+ }
+ }
+
/** Find by query - returns a list */
- def find(query: DBObject) = underlying.find(query).asScala
+ def find(query: DBObject) = sansJodaTime { underlying.find(query).asScala }
/** Find by query - returns a single item */
- def find(id: ObjectId): GridFSDBFile = underlying.find(id)
+ def find(id: ObjectId): GridFSDBFile = sansJodaTime { underlying.find(id) }
/** Find by query - returns a list */
- def find(filename: String) = underlying.find(filename).asScala
- def findOne(query: DBObject): GridFSDBFile = underlying.findOne(query)
- def findOne(id: ObjectId): GridFSDBFile = underlying.findOne(id)
- def findOne(filename: String): GridFSDBFile = underlying.findOne(filename)
+ def find(filename: String) = sansJodaTime { underlying.find(filename).asScala }
+ def findOne(query: DBObject): GridFSDBFile = sansJodaTime { underlying.findOne(query) }
+ def findOne(id: ObjectId): GridFSDBFile = sansJodaTime { underlying.findOne(id) }
+ def findOne(filename: String): GridFSDBFile = sansJodaTime { underlying.findOne(filename) }
def bucketName = underlying.getBucketName
//def db = new ScalaMongoDB(underlying.getDB)
@@ -194,8 +217,8 @@ class GridFS protected[mongodb](val underlying: MongoGridFS) extends Iterable[Gr
* Returns a cursor for this filestore
* of all of the files...
*/
- def files = underlying.getFileList
- def files(query: DBObject) = underlying.getFileList(query)
+ def files = sansJodaTime { underlying.getFileList }
+ def files(query: DBObject) = sansJodaTime { underlying.getFileList(query) }
def remove(query: DBObject) = underlying.remove(query)
def remove(id: ObjectId) = underlying.remove(id)
View
4 src/main/scala/mongodb/Implicits.scala
@@ -236,9 +236,7 @@ trait Implicits extends FluidQueryBarewordOps {
}
-object Implicits extends Implicits {
- conversions.scala.RegisterConversionHelpers()
-}
+object Implicits extends Implicits
object Imports extends Imports
object BaseImports extends BaseImports
object MongoTypeImports extends MongoTypeImports
View
2  src/main/scala/mongodb/MongoConnection.scala
@@ -53,6 +53,8 @@ object MongoConnection {
* @version 1.0
*/
class MongoConnection(val underlying: Mongo) {
+ // Register the core Serialization helpers.
+ conversions.scala.RegisterConversionHelpers()
/**
* Apply method which proxies getDB, allowing you to call
* <code>connInstance("dbName")</code>
View
7 src/main/scala/mongodb/conversions/Helpers.scala
@@ -33,12 +33,13 @@ import org.scala_tools.time.Imports._
trait MongoConversionHelper extends Logging {
+
def register() = {
- log.info("Reached base registration method on MongoConversionHelper")
+ log.info("Reached base registration method on MongoConversionHelper.")
}
+
def unregister() = {
- log.info("Reached base de-registration method on MongoConversionHelper")
+ log.info("Reached base de-registration method on MongoConversionHelper.")
}
}
-
// vim: set ts=2 sw=2 sts=2 et:
View
75 src/main/scala/mongodb/conversions/MonkeyPatches.scala
@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2009, 2010 Novus Partners, Inc. <http://novus.com>
+ *
+ * Licensed 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.
+ *
+ * For questions and comments about this product, please see the project page at:
+ *
+ * http://bitbucket.org/novus/casbah
+ *
+ * NOTICE: Portions of this work are derived from the Apache License 2.0 "mongo-scala-driver" work
+ * by Alexander Azarov <azarov@osinka.ru>, available from http://github.com/alaz/mongo-scala-driver
+ */
+
+import com.novus.casbah.util.Logging
+
+import com.mongodb._
+import org.bson.{BSON, Transformer}
+
+import org.scala_tools.time.Imports._
+
+package org.bson {
+ object BSONTransformMonkeyPatch extends Logging {
+ // Just dumps info out to the log info of what's registered.
+ def dump() = {
+ log.info("Encoding Hooks: %s", org.bson.BSON._encodingHooks)
+ log.info("Decoding Hooks: %s", org.bson.BSON._decodingHooks)
+ }
+ }
+
+ object BSONEncoders extends Logging {
+ // Finds all the transformers for a given class
+ def apply(encodeType: Class[_]) = org.bson.BSON._encodingHooks.get(encodeType) match {
+ case null => None
+ case x => Some(x)
+ }
+ def apply() = org.bson.BSON._encodingHooks
+ //def +=(elems: (Class[_], Transformer)) = elems.foreach { ct =>
+ def remove(encodeType: Class[_]) = apply(encodeType) match {
+ case Some(list) => {
+ log.info("Clearing encoding transform list ['%s'] for class '%s'", list, encodeType)
+ org.bson.BSON._encodingHooks.remove(encodeType) //list.clear()
+ }
+ case None => log.warning("No encoding transformers found registered for class '%s'", encodeType)
+ }
+ }
+
+ object BSONDecoders extends Logging {
+ // Finds all the transformers for a given class
+ def apply(decodeType: Class[_]) = org.bson.BSON._decodingHooks.get(decodeType) match {
+ case null => None
+ case x => Some(x)
+ }
+ def apply() = org.bson.BSON._decodingHooks
+ //def +=(elems: (Class[_], Transformer)) = elems.foreach { ct =>
+ def remove(decodeType: Class[_]) = apply(decodeType) match {
+ case Some(list) => {
+ log.info("Clearing decoding transform list ['%s'] for class '%s'", list, decodeType)
+ org.bson.BSON._decodingHooks.remove(decodeType) //list.clear()
+ }
+ case None => log.warning("No decoding transformers found registered for class '%s'", decodeType)
+ }
+ }
+}
+
+// vim: set ts=2 sw=2 sts=2 et:
View
167 src/main/scala/mongodb/conversions/ScalaConversions.scala
@@ -35,8 +35,8 @@ import org.scala_tools.time.Imports._
/**
* " Register" Object, calls the registration methods.
*
- * By default does not include JodaTime as this may be undesired behavior.
- * If you want JodaTime support, please use the RegisterJodaTimeConversionHelpers Object
+ * By default does not include JodaDateTime as this may be undesired behavior.
+ * If you want JodaDateTime support, please use the RegisterJodaTimeConversionHelpers Object
*
* @author Brendan W. McAdams <bmcadams@novus.com>
* @version 1.0, 06/22/10
@@ -44,7 +44,7 @@ import org.scala_tools.time.Imports._
* @see RegisterJodaTimeConversionHelpers
*/
object RegisterConversionHelpers extends Serializers
- with Deserializers {
+ with Deserializers {
def apply() = {
log.info("Registering Scala Conversions.")
super.register()
@@ -58,14 +58,14 @@ object RegisterConversionHelpers extends Serializers
* @version 1.0, 06/22/10
* @since 1.0
*/
-@deprecated("Be VERY careful using this - it will remove ALL third-party loaded BSON Encoding & Decoding hooks at runtime.")
+@deprecated("Be VERY careful using this - it will remove ALL of Casbah's loaded BSON Encoding & Decoding hooks at runtime. If you need to clear Joda Time use DeregisterJodaTimeConversionHelpers.")
object DeregisterConversionHelpers extends Serializers
with Deserializers {
def apply() = {
log.info("Deregistering Scala Conversions.")
// TODO - Figure out how to clear specific hooks as this clobbers everything.
- log.warning("Clobbering *ALL* Registered BSON Type Hooks. Reregister any specific ones you may need.")
- BSON.clearAllHooks()
+ log.warning("Clobbering Casbah's Registered BSON Type Hooks (EXCEPT Joda Time). Reregister any specific ones you may need.")
+ super.unregister()
}
}
@@ -76,7 +76,7 @@ object DeregisterConversionHelpers extends Serializers
* an explicit invocation / registration of individual
* deserializers, else unexpected behavior will occur.
*
- * Because it's likely to be controversial, JodaTime is NOT mixed in by default.
+ * Because it's likely to be controversial, JodaDateTime is NOT mixed in by default.
*
* @author Brendan W. McAdams <bmcadams@novus.com>
* @version 1.0, 06/22/10
@@ -87,13 +87,9 @@ trait Deserializers extends MongoConversionHelper {
log.info("Deserializers for Scala Conversions registering")
super.register()
}
- /*override def unregister() = {
- log.info("Deserializers for Scala Conversions deregistering")
- // TODO - Find out how to clear a SPECIFIC hook.
- log.warning("Unregistering *ALL* Decoding hooks for MongoDB. ")
- CasbahBSONHelper.clearDecoders
- //super.unregister()
- }*/
+ override def unregister() = {
+ super.unregister()
+ }
}
/**
@@ -104,7 +100,7 @@ trait Deserializers extends MongoConversionHelper {
* Be very careful with the deserializers however as they can come with
* unexpected behavior.
*
- * Because it's likely to be controversial, JodaTime is NOT mixed in by default.
+ * Because it's likely to be controversial, JodaDateTime is NOT mixed in by default.
*
* @author Brendan W. McAdams <bmcadams@novus.com>
* @version 1.0, 06/22/10
@@ -117,110 +113,129 @@ trait Serializers extends MongoConversionHelper
log.info("Serializers for Scala Conversions registering")
super.register()
}
- /*override def unregister() = {
- log.info("Serializers for Scala Conversions deregistering")
- // TODO - Find out how to clear a SPECIFIC hook.
- log.warning("Unregistering *ALL* Encoding hooks for MongoDB. ")
- CasbahBSONHelper.clearDecoders
- //super.unregister()
- }*/
+ override def unregister() = {
+ super.unregister()
+ }
}
-object RegisterJodaTimeConversionHelpers extends JodaTimeHelpers {
+object RegisterJodaTimeConversionHelpers extends JodaDateTimeHelpers {
def apply() = {
- log.info("Registering Scala Conversions.")
+ log.info("Registering Joda Time Scala Conversions.")
super.register()
}
}
-trait JodaTimeHelpers extends JodaTimeSerializer with JodaTimeDeserializer
+object DeregisterJodaTimeConversionHelpers extends JodaDateTimeHelpers {
+ def apply() = {
+ log.info("Unregistering Joda Time Scala Conversions.")
+ super.unregister()
+ }
+}
-trait JodaTimeSerializer extends MongoConversionHelper {
+trait JodaDateTimeHelpers extends JodaDateTimeSerializer with JodaDateTimeDeserializer
- override def register() = {
- log.info("Setting up Joda Time Serializers")
+trait JodaDateTimeSerializer extends MongoConversionHelper {
- log.info("Hooking up Joda DateTime serializer")
- /** Encoding hook for MongoDB To be able to persist JodaTime DateTime to MongoDB */
- BSON.addEncodingHook(classOf[DateTime], new Transformer {
- log.trace("Encoding a JodaTime DateTime.")
+ private val encodeType = classOf[DateTime]
+ /** Encoding hook for MongoDB To be able to persist JodaDateTime DateTime to MongoDB */
+ private val transformer = new Transformer {
+ log.trace("Encoding a JodaDateTime DateTime.")
- def transform(o: AnyRef): AnyRef = o match {
- case d: DateTime => d.toDate // Return a JDK Date object which BSON can encode
- case unknownRef: AnyRef => throw new IllegalArgumentException("Don't know how to serialize an object of type '" + unknownRef.getClass + "'")
- case unknownVal => throw new IllegalArgumentException("Don't know how to serialize '" + unknownVal + "'")
- }
-
- })
+ def transform(o: AnyRef): AnyRef = o match {
+ case d: DateTime => d.toDate // Return a JDK Date object which BSON can encode
+ case unknownRef: AnyRef => throw new IllegalArgumentException("Don't know how to serialize an object of type '" + unknownRef.getClass + "'")
+ case unknownVal => throw new IllegalArgumentException("Don't know how to serialize '" + unknownVal + "'")
+ }
+
+ }
+ override def register() = {
+ log.info("Hooking up Joda DateTime serializer.")
+ /** Encoding hook for MongoDB To be able to persist JodaDateTime DateTime to MongoDB */
+ BSON.addEncodingHook(encodeType, transformer)
super.register()
}
+
+ override def unregister() = {
+ log.info("De-registering Joda DateTime serializer.")
+ org.bson.BSONEncoders.remove(encodeType)
+ super.unregister()
+ }
}
-trait JodaTimeDeserializer extends MongoConversionHelper {
+trait JodaDateTimeDeserializer extends MongoConversionHelper {
- override def register() = {
- log.info("Setting up Joda Time Deserializers")
+ private val encodeType = classOf[java.util.Date]
+ private val transformer = new Transformer {
+ log.trace("Decoding JDK Dates .")
- log.info("Hooking up Joda DateTime deserializer")
- /** Encoding hook for MongoDB To be able to read JodaTime DateTime from MongoDB's BSON Date */
- BSON.addDecodingHook(classOf[java.util.Date], new Transformer {
- log.trace("Decoding JDK Dates .")
-
- def transform(o: AnyRef): AnyRef = o match {
- case jdkDate: java.util.Date => new DateTime(jdkDate)
- case d: DateTime => {
- log.warning("Transformer got an actual JodaTime DateTime?")
- d
- }
- case unknownRef: AnyRef => throw new IllegalArgumentException("Don't know how to serialize an object of type '" + unknownRef.getClass + "'")
- case unknownVal => throw new IllegalArgumentException("Don't know how to serialize '" + unknownVal + "'")
+ def transform(o: AnyRef): AnyRef = o match {
+ case jdkDate: java.util.Date => new DateTime(jdkDate)
+ case d: DateTime => {
+ log.warning("Transformer got an actual JodaDateTime DateTime?")
+ d
}
-
- })
+ case unknownRef: AnyRef => throw new IllegalArgumentException("Don't know how to serialize an object of type '" + unknownRef.getClass + "'")
+ case unknownVal => throw new IllegalArgumentException("Don't know how to serialize '" + unknownVal + "'")
+ }
+ }
+ override def register() = {
+ log.info("Hooking up Joda DateTime deserializer")
+ /** Encoding hook for MongoDB To be able to read JodaDateTime DateTime from MongoDB's BSON Date */
+ BSON.addDecodingHook(encodeType, transformer)
super.register()
}
+
+ override def unregister() = {
+ log.info("De-registering Joda DateTime dserializer.")
+ org.bson.BSONDecoders.remove(encodeType)
+ super.unregister()
+ }
}
trait ScalaRegexSerializer extends MongoConversionHelper {
+ private val transformer = new Transformer {
+ log.trace("Encoding a Scala RegEx.")
+
+ def transform(o: AnyRef): AnyRef = o match {
+ case sRE: _root_.scala.util.matching.Regex => sRE.pattern
+ case _ => o
+ }
+
+ }
override def register() = {
log.info("Setting up ScalaRegexSerializers")
log.info("Hooking up scala.util.matching.Regex serializer")
/** Encoding hook for MongoDB to translate a Scala Regex to a JAva Regex (which Mongo will understand)*/
- BSON.addEncodingHook(classOf[_root_.scala.util.matching.Regex], new Transformer {
- log.trace("Encoding a Scala RegEx.")
+ BSON.addEncodingHook(classOf[_root_.scala.util.matching.Regex], transformer)
- def transform(o: AnyRef): AnyRef = o match {
- case sRE: _root_.scala.util.matching.Regex => sRE.pattern
- case _ => o
- }
-
- })
super.register()
}
}
trait ScalaArrayBufferSerializer extends MongoConversionHelper {
+ private val transformer = new Transformer {
+ import scalaj.collection.Imports._
+ log.debug("Encoding a Scala ArrayBuffer.")
+
+ def transform(o: AnyRef): AnyRef = o match {
+ case ab: _root_.scala.collection.mutable.ArrayBuffer[_] => ab.asJava
+ case _ => o
+ }
+ }
+
override def register() = {
- log.info("Setting up ScalaArrayBufferSerializers")
+ log.debug("Setting up ScalaArrayBufferSerializers")
- log.info("Hooking up scala.collection.mutable.ArrayBuffer serializer")
+ log.debug("Hooking up scala.collection.mutable.ArrayBuffer serializer")
/** Encoding hook for MongoDB to translate a Scala Regex to a JAva Regex (which Mongo will understand)*/
- BSON.addEncodingHook(classOf[_root_.scala.collection.mutable.ArrayBuffer[_]], new Transformer {
- import scalaj.collection.Imports._
- log.debug("Encoding a Scala ArrayBuffer.")
+ BSON.addEncodingHook(classOf[_root_.scala.collection.mutable.ArrayBuffer[_]], transformer)
- def transform(o: AnyRef): AnyRef = o match {
- case ab: _root_.scala.collection.mutable.ArrayBuffer[_] => ab.asJava
- case _ => o
- }
-
- })
super.register()
}
}
View
11 src/main/scala/mongodb/query/CoreOperators.scala
@@ -100,15 +100,26 @@ sealed trait QueryOperator {
protected def op(op: String, target: Any) = dbObj match {
case Some(nested) => {
log.debug("{nested} DBObj: %s Op: %s Target: %s [%s]", dbObj, op, target, target.asInstanceOf[AnyRef].getClass)
+ patchSerialization(target)
nested.put(op, target)
(field -> nested)
}
case None => {
log.debug("DBObj: %s Op: %s Target: %s [%s]", dbObj, op, target, target.asInstanceOf[AnyRef].getClass)
+ patchSerialization(target)
val opMap = BasicDBObjectBuilder.start(op, target).get
(field -> opMap)
}
}
+ /**
+ * Temporary fix code for making sure certain edge cases w/ the serialization libs
+ * Don't happen. This may impose a slight performance penalty.
+ */
+ protected def patchSerialization(target: Any): Unit = target match {
+ case ab: scala.collection.mutable.ArrayBuffer[_] => new conversions.scala.ScalaArrayBufferSerializer { register() }
+ case _ => {}
+ }
+
}
trait NestingQueryHelper extends QueryOperator with Logging {
View
6 src/test/scala/mongodb/ConversionSpec.scala
@@ -37,7 +37,7 @@ import org.scala_tools.time.Imports._
import conversions.scala._
class ConversionSpec extends FeatureSpec with GivenWhenThen with ShouldMatchers with Logging {
- DeregisterConversionHelpers()
+ DeregisterJodaTimeConversionHelpers()
feature("The conversions do not work unless explicitly brought into scope.") {
val conn = new Mongo().asScala
implicit val mongo = conn("conversions")
@@ -67,7 +67,7 @@ class ConversionSpec extends FeatureSpec with GivenWhenThen with ShouldMatchers
}
scenario("And Conversions can be deregistered....") {
given("A Mongo object connected to the default [localhost]")
- DeregisterConversionHelpers()
+ DeregisterJodaTimeConversionHelpers()
assert(conn != null)
log.info("Date: %s", now)
evaluating { mongo("dateDeRegedFail").insert(Map("date" -> now).asDBObject) } should produce [IllegalArgumentException]
@@ -75,7 +75,7 @@ class ConversionSpec extends FeatureSpec with GivenWhenThen with ShouldMatchers
mongo("dateDeRegedFail").insert(Map("date" -> jDate).asDBObject)
and("It should not come back as a Joda DateTime")
val testRow = mongo("dateDeRegedFail").findOne()
- log.info("Test Row: %s", testRow)
+ //log.info("Test Row: %s", testRow)
testRow.get("date").isInstanceOf[java.util.Date]
log.info("JDK Date: %s", testRow.get("date").asInstanceOf[java.util.Date])
evaluating { testRow.get("date").asInstanceOf[DateTime] } should produce [ClassCastException]
View
3  src/test/scala/mongodb/GridFSSpec.scala
@@ -39,7 +39,8 @@ class GridFSSpec extends FeatureSpec with GivenWhenThen with ShouldMatchers with
feature("The map/reduce engine works correctly") {
val conn = new Mongo().asScala
- DeregisterConversionHelpers()
+ // New functionality should forcibly unload Joda Helpers where they conflict; so load them explicitly
+ RegisterJodaTimeConversionHelpers()
//DeRegisterJodaTimeConversionHelpers()
scenario("Error conditions such as a non-existant collection should not blow up but return an error-state result") {
given("A Mongo object connected to the default [localhost]")
Please sign in to comment.
Something went wrong with that request. Please try again.