All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dao.BsonDao.scala Maven / Gradle / Ivy

The newest version!
// Copyright (C) 2014 Fehmi Can Saglam (@fehmicans) and contributors.
// See the LICENCE.txt file distributed with this work for additional
// information regarding copyright ownership.
//
// 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.

package reactivemongo.extensions.dao

import scala.util.Random
import scala.concurrent.{ Await, ExecutionContext, Future }
import scala.concurrent.duration._
import reactivemongo.bson._
import reactivemongo.api.{ DB, DefaultDB, QueryOpts }
import reactivemongo.api.indexes.Index
import reactivemongo.api.commands.{ GetLastError, WriteResult }
import reactivemongo.api.collections.bson.BSONCollection
import reactivemongo.extensions.dsl.BsonDsl._
import play.api.libs.iteratee.{ Enumerator, Iteratee }
import Handlers._

/** A DAO implementation operates on BSONCollection using BSONDocument.
 *
 *  To create a DAO for a concrete model extend this class.
 *
 *  Below is a sample model.
 *  {{{
 *  import reactivemongo.bson._
 *  import reactivemongo.extensions.dao.Handlers._
 *
 *  case class Person(
 *  _id: BSONObjectID = BSONObjectID.generate,
 *  name: String,
 *  surname: String,
 *  age: Int)
 *
 *  object Person {
 *  implicit val personHandler = Macros.handler[Person]
 *  }
 *  }}}
 *
 *  To define a BsonDao for the Person model you just need to extend BsonDao.
 *
 *  {{{
 *  import reactivemongo.api.{ MongoDriver, DB }
 *  import reactivemongo.bson.BSONObjectID
 *  import reactivemongo.bson.DefaultBSONHandlers._
 *  import reactivemongo.extensions.dao.BsonDao
 *  import scala.concurrent.ExecutionContext.Implicits.global
 *
 *  object MongoContext {
 *  val driver = new MongoDriver
 *  val connection = driver.connection(List("localhost"))
 *  def db(): DB = connection("reactivemongo-extensions")
 *  }
 *
 *  object PersonDao extends BsonDao[Person, BSONObjectID](MongoContext.db, "persons")
 *  }}}
 *
 *  @param db A parameterless function returning a [[reactivemongo.api.DB]] instance.
 *  @param collectionName Name of the collection this DAO is going to operate on.
 *  @param modelReader BSONDocumentReader for the Model type.
 *  @param modelWriter BSONDocumentWriter for the Model type.
 *  @param idWriter BSONDocumentWriter for the ID type.
 *  @param lifeCycle [[reactivemongo.extensions.dao.LifeCycle]] for the Model type.
 *  @tparam Model Type of the model that this DAO uses.
 *  @tparam ID Type of the ID field of the model.
 */
abstract class BsonDao[Model, ID](db: => Future[DefaultDB], collectionName: String)(implicit
	modelReader: BSONDocumentReader[Model],
	modelWriter: BSONDocumentWriter[Model],
	idWriter: BSONWriter[ID, _ <: BSONValue],
	idReader: BSONReader[_ <: BSONValue, ID],
	lifeCycle: LifeCycle[Model, ID] = new ReflexiveLifeCycle[Model, ID],
	ec: ExecutionContext)
		extends Dao[BSONCollection, BSONDocument, Model, ID, BSONDocumentWriter](db, collectionName) {

	def ensureIndexes()(implicit ec: ExecutionContext): Future[Traversable[Boolean]] = Future sequence {
		autoIndexes map { index =>
			collection.flatMap(_.indexesManager.ensure(index))
		}
	}.map { results =>
		lifeCycle.ensuredIndexes()
		results
	}

	def listIndexes()(implicit ec: ExecutionContext): Future[List[Index]] =
		collection.flatMap(_.indexesManager.list())

	def findOne(selector: BSONDocument = BSONDocument.empty)(implicit ec: ExecutionContext): Future[Option[Model]] = collection.flatMap(_.find(selector).one[Model])

	def findById(id: ID)(implicit ec: ExecutionContext): Future[Option[Model]] =
		findOne($id(id))

	def findByIds(ids: ID*)(implicit ec: ExecutionContext): Future[List[Model]] =
		findAll("_id" $in (ids: _*))

	def find(
		selector: BSONDocument = BSONDocument.empty,
		sort: BSONDocument = BSONDocument("_id" -> 1),
		page: Int,
		pageSize: Int)(implicit ec: ExecutionContext): Future[List[Model]] = {
		val from = (page - 1) * pageSize
		collection.flatMap(_
			.find(selector)
			.sort(sort)
			.options(QueryOpts(skipN = from, batchSizeN = pageSize))
			.cursor[Model]()
			.collect[List](pageSize))
	}

	def findAll(
		selector: BSONDocument = BSONDocument.empty,
		sort: BSONDocument = BSONDocument("_id" -> 1))(implicit ec: ExecutionContext): Future[List[Model]] =
		collection.flatMap(_.find(selector).sort(sort).cursor[Model]().collect[List]())

	@deprecated(
		since = "0.11.1",
		message = "Directly use [[findAndUpdate]] collection operation")
	def findAndUpdate(
		query: BSONDocument,
		update: BSONDocument,
		sort: BSONDocument = BSONDocument.empty,
		fetchNewObject: Boolean = false,
		upsert: Boolean = false)(implicit ec: ExecutionContext): Future[Option[Model]] = collection.flatMap(_.findAndUpdate(
		query, update, fetchNewObject, upsert).map(_.result[Model]))

	@deprecated(
		since = "0.11.1",
		message = "Directly use [[findAndRemove]] collection operation")
	def findAndRemove(query: BSONDocument, sort: BSONDocument = BSONDocument.empty)(implicit ec: ExecutionContext): Future[Option[Model]] =
		collection.flatMap(_.findAndRemove(
			query, if (sort == BSONDocument.empty) None else Some(sort)).
			map(_.result[Model]))

	def findRandom(selector: BSONDocument = BSONDocument.empty)(implicit ec: ExecutionContext): Future[Option[Model]] = for {
		count <- count(selector)
		index = if (count == 0) 0 else Random.nextInt(count)
		random <- collection.flatMap(_.find(selector).options(QueryOpts(skipN = index, batchSizeN = 1)).one[Model])
	} yield random

	def insert(model: Model, writeConcern: GetLastError = defaultWriteConcern)(implicit ec: ExecutionContext): Future[WriteResult] = {
		val mappedModel = lifeCycle.prePersist(model)
		collection.flatMap(_.insert(mappedModel, writeConcern) map { writeResult =>
			lifeCycle.postPersist(mappedModel)
			writeResult
		})
	}

	//	private val (maxBulkSize, maxBsonSize): (Int, Int) =
	//		collection.db.connection.metadata.map {
	//			metadata => metadata.maxBulkSize -> metadata.maxBsonSize
	//		}.getOrElse[(Int, Int)](Int.MaxValue -> Int.MaxValue)

	def bulkInsert(
		documents: TraversableOnce[Model],
		bulkSize: Int,
		bulkByteSize: Int)(implicit ec: ExecutionContext): Future[Int] = {
		val mappedDocuments = documents.map(lifeCycle.prePersist)
		val writer = implicitly[BSONDocumentWriter[Model]]

		collection.flatMap(_.bulkInsert(
			mappedDocuments.map(writer.write(_)).toStream,
			true, defaultWriteConcern, bulkSize, bulkByteSize) map { result =>
				mappedDocuments.map(lifeCycle.postPersist)
				result.n
			})
	}

	def update[U: BSONDocumentWriter](
		selector: BSONDocument,
		update: U,
		writeConcern: GetLastError = defaultWriteConcern,
		upsert: Boolean = false,
		multi: Boolean = false)(implicit ec: ExecutionContext): Future[WriteResult] = collection.flatMap(_.update(selector, update, writeConcern, upsert, multi))

	def updateById[U: BSONDocumentWriter](
		id: ID,
		update: U,
		writeConcern: GetLastError = defaultWriteConcern)(implicit ec: ExecutionContext): Future[WriteResult] = collection.flatMap(_.update($id(id), update, writeConcern))

	def save(model: Model, writeConcern: GetLastError = defaultWriteConcern)(implicit ec: ExecutionContext): Future[WriteResult] = {
		val writer = implicitly[BSONDocumentWriter[Model]]

		for {
			doc <- Future(writer write model)
			_id <- Future(doc.getAs[ID]("_id").get)
			res <- {
				val mappedModel = lifeCycle.prePersist(model)
				collection.flatMap(_.update(selector = $id(_id), update = mappedModel,
					upsert = true, writeConcern = writeConcern) map { result =>
					lifeCycle.postPersist(mappedModel)
					result
				})
			}
		} yield res
	}

	def count(selector: BSONDocument = BSONDocument.empty)(implicit ec: ExecutionContext): Future[Int] = collection.flatMap(_.count(Some(selector)))

	def drop()(implicit ec: ExecutionContext): Future[Unit] = collection.flatMap(_.drop())

	def dropSync(timeout: Duration = 10 seconds)(implicit ec: ExecutionContext): Unit = Await.result(drop(), timeout)

	def removeById(id: ID, writeConcern: GetLastError = defaultWriteConcern)(implicit ec: ExecutionContext): Future[WriteResult] = {
		lifeCycle.preRemove(id)
		collection.flatMap(_.remove($id(id), writeConcern = defaultWriteConcern) map { res =>
			lifeCycle.postRemove(id)
			res
		})
	}

	def remove(
		query: BSONDocument,
		writeConcern: GetLastError = defaultWriteConcern,
		firstMatchOnly: Boolean = false)(implicit ec: ExecutionContext): Future[WriteResult] = collection.flatMap(_.remove(query, writeConcern, firstMatchOnly))

	def removeAll(writeConcern: GetLastError = defaultWriteConcern)(implicit ec: ExecutionContext): Future[WriteResult] = {
		collection.flatMap(_.remove(selector = BSONDocument.empty, writeConcern = writeConcern, firstMatchOnly = false))
	}

	def foreach(
		selector: BSONDocument = BSONDocument.empty,
		sort: BSONDocument = BSONDocument("_id" -> 1))(f: (Model) => Unit)(implicit ec: ExecutionContext): Future[Unit] = {
		collection.flatMap(_.find(selector).sort(sort).cursor[Model]()
			.enumerate()
			.apply(Iteratee.foreach(f))
			.flatMap(i => i.run))
	}

	def fold[A](
		selector: BSONDocument = BSONDocument.empty,
		sort: BSONDocument = BSONDocument("_id" -> 1),
		state: A)(f: (A, Model) => A)(implicit ec: ExecutionContext): Future[A] = {
		collection.flatMap(_.find(selector).sort(sort).cursor[Model]()
			.enumerate()
			.apply(Iteratee.fold(state)(f))
			.flatMap(i => i.run))
	}

	ensureIndexes()
}

object BsonDao {
	def apply[Model, ID](db: => Future[DefaultDB], collectionName: String)(
		implicit
		modelReader: BSONDocumentReader[Model],
		modelWriter: BSONDocumentWriter[Model],
		idWriter: BSONWriter[ID, _ <: BSONValue],
		idReader: BSONReader[_ <: BSONValue, ID],
		lifeCycle: LifeCycle[Model, ID] = new ReflexiveLifeCycle[Model, ID],
		ec: ExecutionContext): BsonDao[Model, ID] = {
		new BsonDao[Model, ID](db, collectionName) {}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy