diff --git a/README.md b/README.md index f1a0939..176e095 100755 --- a/README.md +++ b/README.md @@ -89,10 +89,23 @@ now we need to setup our connections. The plugin is modeled after how plays DB p # host2.port = 27018 #} + # Mongo Options + # ~~~~~ + # http://api.mongodb.org/java/2.8.0/com/mongodb/MongoOptions.html + # + # For passing custom options to the MongoConnection add the properties under "options". Add just the ones which are different from defaults. + + #mongodb.default.options { + # connectionsPerHost = 100 + # threadsAllowedToBlockForConnectionMultiplier = 1000 + # connectTimeout = 60000 + #} + ## More that one DB? -If you would like to connect to two databases you need to create two source names +If you would like to connect to two databases you need to create two source names. You also can specify different options per database mongodb.myotherdb.db = "otherdb" + mongodb.myotherdb.options.connectionsPerHost = 80 Then you can call `mongoCollection("collectionname", "myotherdb")` diff --git a/src/main/scala/se/radley/plugin/salat/OptionsFromConfig.scala b/src/main/scala/se/radley/plugin/salat/OptionsFromConfig.scala new file mode 100644 index 0000000..fc1ec63 --- /dev/null +++ b/src/main/scala/se/radley/plugin/salat/OptionsFromConfig.scala @@ -0,0 +1,42 @@ +package se.radley.plugin.salat + +import play.api.Configuration +import com.mongodb.casbah.MongoOptions + +object OptionsFromConfig { + + def apply(config: Option[Configuration]): Option[com.mongodb.MongoOptions] = { + if (config.isDefined && config.get.keys.isEmpty) None + else config.map { implicit conf => + val defaults = new com.mongodb.MongoOptions + MongoOptions( + autoConnectRetry = ("autoConnectRetry", defaults.autoConnectRetry), + connectionsPerHost = ("connectionsPerHost", defaults.connectionsPerHost), + threadsAllowedToBlockForConnectionMultiplier = ("threadsAllowedToBlockForConnectionMultiplier", defaults.threadsAllowedToBlockForConnectionMultiplier), + maxWaitTime = ("maxWaitTime", defaults.maxWaitTime), + connectTimeout = ("connectTimeout", defaults.connectTimeout), + socketTimeout = ("socketTimeout", defaults.socketTimeout), + socketKeepAlive = ("socketKeepAlive", defaults.socketKeepAlive), + maxAutoConnectRetryTime = ("maxAutoConnectRetryTime", defaults.maxAutoConnectRetryTime), + slaveOk = ("slaveOk", defaults.slaveOk), + safe = ("safe", defaults.safe), + w = ("w", defaults.w), + wTimeout = ("wtimeout", defaults.wtimeout), + fsync = ("fsync", defaults.fsync), + j = ("j", defaults.j), + dbDecoderFactory = ("dbDecoderFactory", defaults.dbDecoderFactory), + dbEncoderFactory = ("dbEncoderFactory", defaults.dbEncoderFactory), + //socketFactory = ("socketFactory", defaults.socketFactory), FIXME Dependency problem + description = ("description", defaults.description)) + } + } + + implicit def getBoolean(prop: (String, Boolean))(implicit conf: Configuration): Boolean = conf.getBoolean(prop._1) getOrElse prop._2 + implicit def getString(prop: (String, String))(implicit conf: Configuration): String = conf.getString(prop._1) getOrElse prop._2 + implicit def getInt(prop: (String, Int))(implicit conf: Configuration): Int = conf.getInt(prop._1) getOrElse prop._2 + implicit def getLong(prop: (String, Long))(implicit conf: Configuration): Long = conf.getMilliseconds(prop._1) getOrElse prop._2 + implicit def getFactory[F](prop: (String, F))(implicit conf: Configuration): F = { + conf.getString(prop._1).map { name => Class.forName(name).newInstance().asInstanceOf[F] } getOrElse prop._2 + } + +} \ No newline at end of file diff --git a/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala b/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala index 1f5097f..b447bd0 100644 --- a/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala +++ b/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala @@ -7,6 +7,7 @@ import com.mongodb.casbah._ import com.mongodb.{MongoException, ServerAddress} import com.mongodb.casbah.gridfs.GridFS import commons.MongoDBObject +import com.mongodb.MongoOptions class SalatPlugin(app: Application) extends Plugin { @@ -18,12 +19,13 @@ class SalatPlugin(app: Application) extends Plugin { val writeConcern: com.mongodb.WriteConcern, val user: Option[String] = None, val password: Option[String] = None, + val options: Option[MongoOptions], private var conn: MongoConnection = null ){ def connection: MongoConnection = { if (conn == null) { - conn = MongoConnection(hosts) + conn = options.map(opts => MongoConnection(hosts, opts)).getOrElse(MongoConnection(hosts)) val authOpt = for { u <- user @@ -68,12 +70,13 @@ class SalatPlugin(app: Application) extends Plugin { override def toString() = { (if (user.isDefined) user.get + "@" else "") + hosts.map(h => h.getHost + ":" + h.getPort).mkString(", ") + - "/" + dbName + "/" + dbName + options.map(" with Options[" + _ + "]").getOrElse("") } } lazy val sources: Map[String, MongoSource] = configuration.subKeys.map { sourceKey => val source = configuration.getConfig(sourceKey).getOrElse(Configuration.empty) + val options = OptionsFromConfig(source.getConfig("options")) source.getString("uri").map { str => // MongoURI config - http://www.mongodb.org/display/DOCS/Connections @@ -90,7 +93,7 @@ class SalatPlugin(app: Application) extends Plugin { val writeConcern = uri.options.getWriteConcern val user = uri.username val password = uri.password.map(_.mkString).filterNot(_.isEmpty) - sourceKey -> MongoSource(hosts, db, writeConcern, user, password) + sourceKey -> MongoSource(hosts, db, writeConcern, user, password, options) }.getOrElse { val dbName = source.getString("db").getOrElse(throw configuration.reportError("mongodb." + sourceKey + ".db", "db missing for source[" + sourceKey + "]")) @@ -114,9 +117,9 @@ class SalatPlugin(app: Application) extends Plugin { // If there are replicasets configured go with those otherwise fallback to simple config if (hosts.isEmpty) - sourceKey -> MongoSource(List(new ServerAddress(host, port)), dbName, writeConcern, user, password) + sourceKey -> MongoSource(List(new ServerAddress(host, port)), dbName, writeConcern, user, password, options) else - sourceKey -> MongoSource(hosts, dbName, writeConcern, user, password) + sourceKey -> MongoSource(hosts, dbName, writeConcern, user, password, options) } }.toMap diff --git a/src/test/scala/se/radley/plugin/salat/OptionsFromConfigSpec.scala b/src/test/scala/se/radley/plugin/salat/OptionsFromConfigSpec.scala new file mode 100644 index 0000000..390b9fd --- /dev/null +++ b/src/test/scala/se/radley/plugin/salat/OptionsFromConfigSpec.scala @@ -0,0 +1,125 @@ +package se.radley.plugin.salat + +import org.specs2.mutable.Specification +import com.mongodb.MongoOptions +import play.api.Configuration +import com.mongodb.DBDecoderFactory +import com.mongodb.DBEncoderFactory +import javax.net.SocketFactory +import com.mongodb.DBDecoder +import org.specs2.mutable.SpecificationWithJUnit +import org.specs2.specification.AllExpectations +import scala.reflect.ClassManifest + +class OptionsFromConfigSpec extends SpecificationWithJUnit with AllExpectations { + + "OptionsFromConfig" should { + "Override all defaults when all props are present" in { + val allNonDefaultConfiguration = Map( + ("mongodb.default.options.autoConnectRetry" -> "true"), + ("mongodb.default.options.connectionsPerHost" -> "333"), + ("mongodb.default.options.threadsAllowedToBlockForConnectionMultiplier" -> "22"), + ("mongodb.default.options.maxWaitTime" -> "68000"), + ("mongodb.default.options.connectTimeout" -> "34000"), + ("mongodb.default.options.socketTimeout" -> "21000"), + ("mongodb.default.options.socketKeepAlive" -> "true"), + ("mongodb.default.options.maxAutoConnectRetryTime" -> "20"), + ("mongodb.default.options.slaveOk" -> "true"), + ("mongodb.default.options.safe" -> "true"), + ("mongodb.default.options.w" -> "1"), + ("mongodb.default.options.wtimeout" -> "10"), + ("mongodb.default.options.fsync" -> "true"), + ("mongodb.default.options.j" -> "true"), + ("mongodb.default.options.dbDecoderFactory" -> "se.radley.plugin.salat.NonDefaultDBDecoderFactory"), + ("mongodb.default.options.dbEncoderFactory" -> "se.radley.plugin.salat.NonDefaultDBEncoderFactory"), + ("mongodb.default.options.socketFactory" -> "se.radley.plugin.salat.NonDefaultDBSocketFactory"), + ("mongodb.default.options.description" -> "Some Description")) + + val sourceConfig = Configuration.from(allNonDefaultConfiguration).getConfig("mongodb.default").get + val optionsConfig = sourceConfig.getConfig("options") + val optionsOpt = OptionsFromConfig(optionsConfig) + optionsOpt must beSome + val options = optionsOpt.get + // All Overridden + options.autoConnectRetry must beTrue + options.connectionsPerHost must be equalTo(333) + options.threadsAllowedToBlockForConnectionMultiplier must be equalTo(22) + options.maxWaitTime must be equalTo(68000) + options.connectTimeout must be equalTo(34000) + options.socketTimeout must be equalTo(21000) + options.socketKeepAlive must beTrue + options.maxAutoConnectRetryTime must be equalTo(20) + options.slaveOk must beTrue + options.safe must beTrue + options.w must be equalTo(1) + options.wtimeout must be equalTo(10) + options.fsync must beTrue + options.j must beTrue + options.description must be equalTo("Some Description") + options.dbDecoderFactory must haveClass[NonDefaultDBDecoderFactory] + options.dbEncoderFactory must haveClass[NonDefaultDBEncoderFactory] + //options.socketFactory must haveClass[NonDefaultSocketFactory] FIXME Dependency problem + } + + "Override some defaults for present props" in { + val someNonDefaultConfiguration = Map( + ("mongodb.default.options.connectionsPerHost" -> "255"), + ("mongodb.default.options.threadsAllowedToBlockForConnectionMultiplier" -> "24"), + ("mongodb.default.options.connectTimeout" -> "60000")) + + val defaultOptions = new MongoOptions + + val sourceConfig = Configuration.from(someNonDefaultConfiguration).getConfig("mongodb.default").get + val optionsConfig = sourceConfig.getConfig("options") + val optionsOpt = OptionsFromConfig(optionsConfig) + optionsOpt must beSome + val options = optionsOpt.get + // Overridden + options.connectionsPerHost must be equalTo(255) + options.threadsAllowedToBlockForConnectionMultiplier must be equalTo(24) + options.connectTimeout must be equalTo(60000) + // Remain defaults + options.autoConnectRetry must beFalse + options.maxWaitTime must be equalTo(1000 * 60 * 2) + options.socketTimeout must be equalTo(0) + options.socketKeepAlive must beFalse + options.maxAutoConnectRetryTime must be equalTo(0) + options.slaveOk must beFalse + options.safe must beFalse + options.w must be equalTo(0) + options.wtimeout must be equalTo(0) + options.fsync must beFalse + options.j must beFalse + options.description must beNull + options.dbDecoderFactory must be(defaultOptions.dbDecoderFactory) + options.dbEncoderFactory must be(defaultOptions.dbEncoderFactory) + options.socketFactory must be(defaultOptions.socketFactory) + } + + "Return none options if config is not defined" in { + val undefinedConfig = None + val options = OptionsFromConfig(undefinedConfig) + options must beNone + } + + "Return none options if config is empty" in { + val emptyConfig = Some(Configuration.empty) + val options = OptionsFromConfig(emptyConfig) + options must beNone + } + } + +} + +class NonDefaultDBDecoderFactory extends DBDecoderFactory { + def create() = null +} +class NonDefaultDBEncoderFactory extends DBEncoderFactory { + def create() = null +} +class NonDefaultSocketFactory extends SocketFactory { + def createSocket(host: String, port: Int) = null + def createSocket(address: java.net.InetAddress, port: Int) = null + def createSocket(host: String, port: Int, clientAddress: java.net.InetAddress, clientPort: Int) = null + def createSocket(address: java.net.InetAddress, port: Int, clientAddress: java.net.InetAddress, clientPort: Int) = null +} \ No newline at end of file diff --git a/src/test/scala/se/radley/plugin/salat/SalatSpec.scala b/src/test/scala/se/radley/plugin/salat/SalatSpec.scala index af9240e..4985a31 100644 --- a/src/test/scala/se/radley/plugin/salat/SalatSpec.scala +++ b/src/test/scala/se/radley/plugin/salat/SalatSpec.scala @@ -13,17 +13,14 @@ import com.mongodb.ServerAddress object SalatSpec extends Specification { lazy val salatApp = FakeApplication( - additionalPlugins = Seq("se.radley.plugin.salat.SalatPlugin") - ) + additionalPlugins = Seq("se.radley.plugin.salat.SalatPlugin")) "Salat Plugin with basic config" should { lazy val app = salatApp.copy( additionalConfiguration = Map( ("mongodb.default.db" -> "salat-test"), - ("mongodb.default.writeconcern" -> "normal") - ) - ) + ("mongodb.default.writeconcern" -> "normal"))) lazy val salat = app.plugin[SalatPlugin].get @@ -59,9 +56,7 @@ object SalatSpec extends Specification { lazy val app = salatApp.copy( additionalConfiguration = Map( - ("mongodb.default.uri" -> "mongodb://127.0.0.1:27017/salat-test") - ) - ) + ("mongodb.default.uri" -> "mongodb://127.0.0.1:27017/salat-test"))) lazy val salat = app.plugin[SalatPlugin].get @@ -95,9 +90,7 @@ object SalatSpec extends Specification { "Salat Plugin with multiple uri config" should { lazy val app = salatApp.copy( additionalConfiguration = Map( - ("mongodb.default.uri" -> "mongodb://127.0.0.1:27017,mongodb.org:1337/salat-test") - ) - ) + ("mongodb.default.uri" -> "mongodb://127.0.0.1:27017,mongodb.org:1337/salat-test"))) lazy val salat = app.plugin[SalatPlugin].get @@ -143,4 +136,35 @@ object SalatSpec extends Specification { } } } + + "Salat Plugin with options" should { + + lazy val app = salatApp.copy( + additionalConfiguration = Map( + ("mongodb.default.db" -> "salat-with-options"), + ("mongodb.default.options.connectionsPerHost" -> "255"), + ("mongodb.default.options.threadsAllowedToBlockForConnectionMultiplier" -> "24"))) + + lazy val salat = app.plugin[SalatPlugin].get + + running(app) { + "start" in { + salat must beAnInstanceOf[SalatPlugin] + } + + "return a MongoCollection" in { + val col = salat.collection("salat-collection") + col must beAnInstanceOf[MongoCollection] + } + + "set mongo options" in { + val col = salat.collection("salat-collection") + val options = col.db.underlying.getMongo().getMongoOptions() + options.connectionsPerHost must equalTo(255) + options.threadsAllowedToBlockForConnectionMultiplier must equalTo(24) + } + + } + } + }