PageRenderTime 75ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/akka-persistence/akka-persistence-mongo/src/main/scala/MongoStorageBackend.scala

https://github.com/yllan/akka
Scala | 229 lines | 162 code | 37 blank | 30 comment | 19 complexity | 4d9da377e7a6c800acd38ff1b20e6c9c MD5 | raw file
Possible License(s): Apache-2.0
  1. /**
  2. * Copyright (C) 2009-2010 Scalable Solutions AB <http://scalablesolutions.se>
  3. */
  4. package se.scalablesolutions.akka.persistence.mongo
  5. import se.scalablesolutions.akka.stm._
  6. import se.scalablesolutions.akka.persistence.common._
  7. import se.scalablesolutions.akka.util.Logging
  8. import se.scalablesolutions.akka.config.Config.config
  9. import com.novus.casbah.mongodb.Imports._
  10. /**
  11. * A module for supporting MongoDB based persistence.
  12. * <p/>
  13. * The module offers functionality for:
  14. * <li>Persistent Maps</li>
  15. * <li>Persistent Vectors</li>
  16. * <li>Persistent Refs</li>
  17. * <p/>
  18. * @author <a href="http://debasishg.blogspot.com">Debasish Ghosh</a>
  19. */
  20. private[akka] object MongoStorageBackend extends
  21. MapStorageBackend[Array[Byte], Array[Byte]] with
  22. VectorStorageBackend[Array[Byte]] with
  23. RefStorageBackend[Array[Byte]] with
  24. Logging {
  25. val KEY = "__key"
  26. val REF = "__ref"
  27. val COLLECTION = "akka_coll"
  28. val HOSTNAME = config.getString("akka.storage.mongodb.hostname", "127.0.0.1")
  29. val DBNAME = config.getString("akka.storage.mongodb.dbname", "testdb")
  30. val PORT = config.getInt("akka.storage.mongodb.port", 27017)
  31. val db: MongoDB = MongoConnection(HOSTNAME, PORT)(DBNAME)
  32. val coll: MongoCollection = db(COLLECTION)
  33. def drop() { db.dropDatabase() }
  34. def insertMapStorageEntryFor(name: String, key: Array[Byte], value: Array[Byte]) {
  35. insertMapStorageEntriesFor(name, List((key, value)))
  36. }
  37. def insertMapStorageEntriesFor(name: String, entries: List[(Array[Byte], Array[Byte])]) {
  38. db.safely { db =>
  39. val q: DBObject = MongoDBObject(KEY -> name)
  40. coll.findOne(q) match {
  41. case Some(dbo) =>
  42. entries.foreach { case (k, v) => dbo += new String(k) -> v }
  43. db.safely { db => coll.update(q, dbo, true, false) }
  44. case None =>
  45. val builder = MongoDBObject.newBuilder
  46. builder += KEY -> name
  47. entries.foreach { case (k, v) => builder += new String(k) -> v }
  48. coll += builder.result.asDBObject
  49. }
  50. }
  51. }
  52. def removeMapStorageFor(name: String): Unit = {
  53. val q: DBObject = MongoDBObject(KEY -> name)
  54. db.safely { db => coll.remove(q) }
  55. }
  56. private def queryFor[T](name: String)(body: (MongoDBObject, Option[DBObject]) => T): T = {
  57. val q = MongoDBObject(KEY -> name)
  58. body(q, coll.findOne(q))
  59. }
  60. def removeMapStorageFor(name: String, key: Array[Byte]): Unit = queryFor(name) { (q, dbo) =>
  61. dbo.foreach { d =>
  62. d -= new String(key)
  63. db.safely { db => coll.update(q, d, true, false) }
  64. }
  65. }
  66. def getMapStorageEntryFor(name: String, key: Array[Byte]): Option[Array[Byte]] = queryFor(name) { (q, dbo) =>
  67. dbo.map { d =>
  68. d.getAs[Array[Byte]](new String(key))
  69. }.getOrElse(None)
  70. }
  71. def getMapStorageSizeFor(name: String): Int = queryFor(name) { (q, dbo) =>
  72. dbo.map { d =>
  73. d.size - 2 // need to exclude object id and our KEY
  74. }.getOrElse(0)
  75. }
  76. def getMapStorageFor(name: String): List[(Array[Byte], Array[Byte])] = queryFor(name) { (q, dbo) =>
  77. dbo.map { d =>
  78. for {
  79. (k, v) <- d.toList
  80. if k != "_id" && k != KEY
  81. } yield (k.getBytes, v.asInstanceOf[Array[Byte]])
  82. }.getOrElse(List.empty[(Array[Byte], Array[Byte])])
  83. }
  84. def getMapStorageRangeFor(name: String, start: Option[Array[Byte]],
  85. finish: Option[Array[Byte]],
  86. count: Int): List[(Array[Byte], Array[Byte])] = queryFor(name) { (q, dbo) =>
  87. dbo.map { d =>
  88. // get all keys except the special ones
  89. val keys = d.keys
  90. .filter(k => k != "_id" && k != KEY)
  91. .toList
  92. .sortWith(_ < _)
  93. // if the supplied start is not defined, get the head of keys
  94. val s = start.map(new String(_)).getOrElse(keys.head)
  95. // if the supplied finish is not defined, get the last element of keys
  96. val f = finish.map(new String(_)).getOrElse(keys.last)
  97. // slice from keys: both ends inclusive
  98. val ks = keys.slice(keys.indexOf(s), scala.math.min(count, keys.indexOf(f) + 1))
  99. ks.map(k => (k.getBytes, d.getAs[Array[Byte]](k).get))
  100. }.getOrElse(List.empty[(Array[Byte], Array[Byte])])
  101. }
  102. def insertVectorStorageEntryFor(name: String, element: Array[Byte]) = {
  103. insertVectorStorageEntriesFor(name, List(element))
  104. }
  105. def insertVectorStorageEntriesFor(name: String, elements: List[Array[Byte]]) = {
  106. // lookup with name
  107. val q: DBObject = MongoDBObject(KEY -> name)
  108. db.safely { db =>
  109. coll.findOne(q) match {
  110. // exists : need to update
  111. case Some(dbo) =>
  112. dbo -= KEY
  113. dbo -= "_id"
  114. val listBuilder = MongoDBList.newBuilder
  115. // expensive!
  116. listBuilder ++= (elements ++ dbo.toSeq.sortWith((e1, e2) => (e1._1.toInt < e2._1.toInt)).map(_._2))
  117. val builder = MongoDBObject.newBuilder
  118. builder += KEY -> name
  119. builder ++= listBuilder.result
  120. coll.update(q, builder.result.asDBObject, true, false)
  121. // new : just add
  122. case None =>
  123. val listBuilder = MongoDBList.newBuilder
  124. listBuilder ++= elements
  125. val builder = MongoDBObject.newBuilder
  126. builder += KEY -> name
  127. builder ++= listBuilder.result
  128. coll += builder.result.asDBObject
  129. }
  130. }
  131. }
  132. def updateVectorStorageEntryFor(name: String, index: Int, elem: Array[Byte]) = queryFor(name) { (q, dbo) =>
  133. dbo.foreach { d =>
  134. d += ((index.toString, elem))
  135. db.safely { db => coll.update(q, d, true, false) }
  136. }
  137. }
  138. def getVectorStorageEntryFor(name: String, index: Int): Array[Byte] = queryFor(name) { (q, dbo) =>
  139. dbo.map { d =>
  140. d(index.toString).asInstanceOf[Array[Byte]]
  141. }.getOrElse(Array.empty[Byte])
  142. }
  143. /**
  144. * if <tt>start</tt> and <tt>finish</tt> both are defined, ignore <tt>count</tt> and
  145. * report the range [start, finish)
  146. * if <tt>start</tt> is not defined, assume <tt>start</tt> = 0
  147. * if <tt>start</tt> == 0 and <tt>finish</tt> == 0, return an empty collection
  148. */
  149. def getVectorStorageRangeFor(name: String, start: Option[Int], finish: Option[Int], count: Int): List[Array[Byte]] = queryFor(name) { (q, dbo) =>
  150. dbo.map { d =>
  151. val ls = d.filter { case (k, v) => k != KEY && k != "_id" }
  152. .toSeq
  153. .sortWith((e1, e2) => (e1._1.toInt < e2._1.toInt))
  154. .map(_._2)
  155. val st = start.getOrElse(0)
  156. val cnt =
  157. if (finish.isDefined) {
  158. val f = finish.get
  159. if (f >= st) (f - st) else count
  160. }
  161. else count
  162. if (st == 0 && cnt == 0) List()
  163. ls.slice(st, st + cnt).asInstanceOf[List[Array[Byte]]]
  164. }.getOrElse(List.empty[Array[Byte]])
  165. }
  166. def getVectorStorageSizeFor(name: String): Int = queryFor(name) { (q, dbo) =>
  167. dbo.map { d => d.size - 2 }.getOrElse(0)
  168. }
  169. def insertRefStorageFor(name: String, element: Array[Byte]) = {
  170. // lookup with name
  171. val q: DBObject = MongoDBObject(KEY -> name)
  172. db.safely { db =>
  173. coll.findOne(q) match {
  174. // exists : need to update
  175. case Some(dbo) =>
  176. dbo += ((REF, element))
  177. coll.update(q, dbo, true, false)
  178. // not found : make one
  179. case None =>
  180. val builder = MongoDBObject.newBuilder
  181. builder += KEY -> name
  182. builder += REF -> element
  183. coll += builder.result.asDBObject
  184. }
  185. }
  186. }
  187. def getRefStorageFor(name: String): Option[Array[Byte]] = queryFor(name) { (q, dbo) =>
  188. dbo.map { d =>
  189. d.getAs[Array[Byte]](REF)
  190. }.getOrElse(None)
  191. }
  192. }