From ae278a73576ec92cd6122a5cd6ade65aa15fc17b Mon Sep 17 00:00:00 2001 From: Juan Uys Date: Tue, 22 May 2012 12:40:11 +0100 Subject: [PATCH] Add MongoURI support --- .../se/radley/plugin/salat/SalatPlugin.scala | 83 ++++++++++---- .../se/radley/plugin/salat/SalatSpec.scala | 103 ++++++++++++++++++ 2 files changed, 167 insertions(+), 19 deletions(-) diff --git a/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala b/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala index 47de068..53600f7 100644 --- a/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala +++ b/src/main/scala/se/radley/plugin/salat/SalatPlugin.scala @@ -3,7 +3,7 @@ package se.radley.plugin.salat import play.api._ import play.api.mvc._ import play.api.Play.current -import com.mongodb.casbah.{WriteConcern, MongoCollection, MongoConnection} +import com.mongodb.casbah.{WriteConcern, MongoCollection, MongoConnection, MongoURI} import com.mongodb.ServerAddress class SalatPlugin(app: Application) extends Plugin { @@ -39,26 +39,71 @@ class SalatPlugin(app: Application) extends Plugin { "/" + db } } + + /** + * Extracts host as String and port as Int from host string + * and defaults port to 27017 if it doesn't exist. + * + * E.g. localhost:9999 returns (localhost, 9999) + * E.g. localhost returns (localhost, 27017) + */ + def hostAndPort(host: String): (String, Int) = host.contains(':') match { + case true => { + val Array(h,p) = host.split(':') + (h,p.toInt) + } + case false => (host, 27017) + } lazy val sources: Map[String, MongoSource] = configuration.subKeys.map { sourceKey => val source = configuration.getConfig(sourceKey).getOrElse(Configuration.empty) - val db = source.getString("db").getOrElse(throw configuration.reportError("mongodb." + sourceKey + ".db", "db missing for source[" + sourceKey + "]")) - - // Simple config - val host = source.getString("host").getOrElse("127.0.0.1") - val port = source.getInt("port").getOrElse(27017) - val user:Option[String] = source.getString("user") - val password:Option[String] = source.getString("password") - - // Replica set config - val hosts: List[ServerAddress] = source.getConfig("replicaset").map { replicaset => - replicaset.subKeys.map { hostKey => - val c = replicaset.getConfig(hostKey).get - val host = c.getString("host").getOrElse(throw configuration.reportError("mongodb." + sourceKey + ".replicaset", "host missing for replicaset in source[" + sourceKey + "]")) - val port = c.getInt("port").getOrElse(27017) - new ServerAddress(host, port) - }.toList - }.getOrElse(List.empty) + + // support MongoURI as per http://www.mongodb.org/display/DOCS/Connections + val (host, port, user, password, hosts, db) = source.getString("uri").map{ uri => { + val all = MongoURI(uri) + val (host,port) = hostAndPort(all.hosts(0)) + val user = all.username match { + case "null" => None + case null => None + case s => Some(s) + } + + val password = all.password match { + case null => None + case s => all.password.foldLeft("")(_ + _.toString) match { + case "" => None + case s => Some(s) + } + } + + // to List[ServerAddress] + val hosts = all.hosts.map(host => { + val (h,p) = hostAndPort(host) + new ServerAddress(h, p) + }) + + (host, port, user, password, hosts.toList, all.database) + }}.getOrElse{ + val db = source.getString("db").getOrElse(throw configuration.reportError("mongodb." + sourceKey + ".db", "db missing for source[" + sourceKey + "]")) + + // Simple config + val host = source.getString("host").getOrElse("127.0.0.1") + val port = source.getInt("port").getOrElse(27017) + val user:Option[String] = source.getString("user") + val password:Option[String] = source.getString("password") + + // Replica set config + val hosts: List[ServerAddress] = source.getConfig("replicaset").map { replicaset => + replicaset.subKeys.map { hostKey => + val c = replicaset.getConfig(hostKey).get + val host = c.getString("host").getOrElse(throw configuration.reportError("mongodb." + sourceKey + ".replicaset", "host missing for replicaset in source[" + sourceKey + "]")) + val port = c.getInt("port").getOrElse(27017) + new ServerAddress(host, port) + }.toList + }.getOrElse(List.empty) + + (host, port, user, password, hosts, db) + } val writeConcern = WriteConcern.valueOf(source.getString("writeconcern", Some(Set("fsyncsafe", "replicassafe", "safe", "normal"))).getOrElse("safe")) @@ -66,7 +111,7 @@ class SalatPlugin(app: Application) extends Plugin { if (hosts.isEmpty) sourceKey -> MongoSource(List(new ServerAddress(host, port)), db, writeConcern, user, password) else - sourceKey -> MongoSource(hosts, db, writeConcern) + sourceKey -> MongoSource(hosts, db, writeConcern, user, password) }.toMap override def enabled = !configuration.subKeys.isEmpty diff --git a/src/test/scala/se/radley/plugin/salat/SalatSpec.scala b/src/test/scala/se/radley/plugin/salat/SalatSpec.scala index 1e5fd11..fb44fc8 100644 --- a/src/test/scala/se/radley/plugin/salat/SalatSpec.scala +++ b/src/test/scala/se/radley/plugin/salat/SalatSpec.scala @@ -29,6 +29,27 @@ object SalatSpec extends Specification { ("mongodb.default.replicaset.host2.host" -> "10.0.0.2") ) ) + + lazy val fakeAppFromURI = FakeApplication( + additionalPlugins = Seq("se.radley.plugin.salat.SalatPlugin"), + additionalConfiguration = Map( + ("mongodb.default.uri" -> "mongodb://127.0.0.1:27017/salat-test") + ) + ) + + lazy val fakeAppFromURIs = FakeApplication( + additionalPlugins = Seq("se.radley.plugin.salat.SalatPlugin"), + additionalConfiguration = Map( + ("mongodb.default.uri" -> "mongodb://127.0.0.1:27017,mongodb.org:1337/salat-test") + ) + ) + + lazy val fakeAppFromURIsWithAuth = FakeApplication( + additionalPlugins = Seq("se.radley.plugin.salat.SalatPlugin"), + additionalConfiguration = Map( + ("mongodb.default.uri" -> "mongodb://nyancat:ILoveMyKittens@127.0.0.1:27017,mongodb.org:1337,192.168.88.99:27000/salat-test") + ) + ) def salat = fakeApp.plugin[SalatPlugin].get @@ -76,5 +97,87 @@ object SalatSpec extends Specification { salat.collection("salat-collection", "sourcethatdoesntexist") must throwAn[PlayException] } } + + // tests with a single URI defined + "start with URI only" in { + running(fakeAppFromURI) { + salat must beAnInstanceOf[SalatPlugin] + } + } + + "return a MongoCollection with URI only" in { + running(fakeAppFromURI) { + val col = salat.collection("salat-collection") + col must beAnInstanceOf[MongoCollection] + } + } + + "set replicasets with URI only" in { + running(fakeAppFromURI) { + def s = fakeAppFromURI.plugin[SalatPlugin].get + s must beAnInstanceOf[SalatPlugin] + val source = s.source("default") + source.hosts must equalTo(List(new ServerAddress("127.0.0.1", 27017))) + } + } + + // tests with multiple URIs defined + "start with URIs only" in { + running(fakeAppFromURIs) { + salat must beAnInstanceOf[SalatPlugin] + } + } + + "return a MongoCollection with URIs only" in { + running(fakeAppFromURIs) { + val col = salat.collection("salat-collection") + col must beAnInstanceOf[MongoCollection] + } + } + + "set replicasets with URIs only" in { + running(fakeAppFromURIs) { + def s = fakeAppFromURIs.plugin[SalatPlugin].get + s must beAnInstanceOf[SalatPlugin] + val source = s.source("default") + source.hosts must equalTo(List(new ServerAddress("127.0.0.1", 27017), new ServerAddress("mongodb.org", 1337))) + } + } + + // tests with multiple authenticated URIs defined + "start with authenticated URIs only" in { + running(fakeAppFromURIsWithAuth) { + salat must beAnInstanceOf[SalatPlugin] + } + } + + "return a MongoCollection with authenticated URIs only" in { + running(fakeAppFromURIsWithAuth) { + val col = salat.collection("salat-collection") + col must beAnInstanceOf[MongoCollection] + } + } + + "set replicasets with authenticated URIs only" in { + running(fakeAppFromURIsWithAuth) { + def s = fakeAppFromURIsWithAuth.plugin[SalatPlugin].get + s must beAnInstanceOf[SalatPlugin] + val source = s.source("default") + source.hosts must equalTo(List(new ServerAddress("127.0.0.1", 27017), + new ServerAddress("mongodb.org", 1337), + new ServerAddress("192.168.88.99", 27000) + )) + } + } + + "should propagate authentication credentials" in { + running(fakeAppFromURIsWithAuth) { + def s = fakeAppFromURIsWithAuth.plugin[SalatPlugin].get + s must beAnInstanceOf[SalatPlugin] + val source = s.source("default") + source.user must equalTo(Some("nyancat")) + source.password must equalTo(Some("ILoveMyKittens")) + } + } } }