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

ongo.reactivemongo-play-json_2.11.0.11.12-play24.source-code.collection.scala Maven / Gradle / Ivy

/*
 * Copyright 2012-2013 Stephane Godbillon (@sgodbillon)
 *
 * 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.play.json.collection

import scala.concurrent.{ ExecutionContext, Future }

import play.api.libs.json.{
  Json,
  JsArray,
  JsBoolean,
  JsError,
  JsObject,
  JsPath,
  JsUndefined,
  JsSuccess,
  Writes
}, Json.JsValueWrapper

import reactivemongo.api.{
  Collection,
  CollectionMetaCommands,
  DB,
  FailoverStrategy,
  QueryOpts,
  ReadPreference
}
import reactivemongo.api.collections.{
  BatchCommands,
  GenericCollection,
  GenericCollectionProducer,
  GenericQueryBuilder
}
import reactivemongo.api.commands.{ WriteConcern, WriteResult }
import reactivemongo.util.option

import reactivemongo.play.json.{ BSONFormats, JSONSerializationPack }

/**
 * A Collection that interacts with the Play JSON library, using `Reads` and `Writes`.
 */
object `package` {
  implicit object JSONCollectionProducer extends GenericCollectionProducer[JSONSerializationPack.type, JSONCollection] {
    def apply(db: DB, name: String, failoverStrategy: FailoverStrategy) = new JSONCollection(db, name, failoverStrategy)
  }
}

object JSONBatchCommands
    extends BatchCommands[JSONSerializationPack.type] { commands =>

  import play.api.libs.json.{
    JsError,
    JsNull,
    JsNumber,
    JsValue,
    JsObject,
    JsString,
    JsResult,
    JsSuccess,
    Reads,
    __
  }
  import reactivemongo.bson.{
    BSONArray,
    BSONDocument,
    BSONDocumentWriter,
    BSONObjectID,
    BSONValue,
    Producer
  }
  import reactivemongo.api.commands.{
    CountCommand => CC,
    DefaultWriteResult,
    DeleteCommand => DC,
    GetLastError => GLE,
    InsertCommand => IC,
    DistinctCommand => DistC,
    LastError,
    ResolvedCollectionCommand,
    UpdateCommand => UC,
    Upserted,
    UpdateWriteResult,
    WriteError,
    WriteConcernError
  }
  import reactivemongo.core.protocol.MongoWireVersion
  import reactivemongo.play.json.readOpt
  import reactivemongo.core.protocol.MongoWireVersion
  import reactivemongo.play.json.commands.CommonImplicits

  val pack = JSONSerializationPack

  object JSONCountCommand extends CC[JSONSerializationPack.type] {
    val pack = commands.pack
  }
  val CountCommand = JSONCountCommand

  object JSONDistinctCommand extends DistC[JSONSerializationPack.type] {
    val pack = commands.pack
  }
  val DistinctCommand = JSONDistinctCommand

  implicit object DistinctWriter
      extends pack.Writer[ResolvedCollectionCommand[DistinctCommand.Distinct]] {

    import CommonImplicits.ReadConcernWriter

    def writes(cmd: ResolvedCollectionCommand[DistinctCommand.Distinct]): pack.Document = {
      val c = Json.obj(
        "distinct" -> cmd.collection,
        "key" -> cmd.command.keyString,
        "query" -> cmd.command.query
      )

      if (cmd.command.version < MongoWireVersion.V32) c else {
        c + ("readConcern" -> ReadConcernWriter.writes(cmd.command.readConcern))
      }
    }
  }

  implicit object DistinctResultReader
      extends pack.Reader[DistinctCommand.DistinctResult] {

    import scala.collection.immutable.ListSet

    private val path = JsPath \ "values"

    def reads(js: JsValue): JsResult[DistinctCommand.DistinctResult] =
      (js \ "values").toEither match {
        case Right(JsArray(values)) =>
          JsSuccess(DistinctCommand.DistinctResult(
            ListSet.empty[JsValue] ++ values
          ))

        case Right(v)    => JsError(path, s"invalid JSON: $v")
        case Left(error) => JsError(Seq(path -> Seq(error)))
      }
  }

  implicit object HintWriter extends Writes[CountCommand.Hint] {
    import CountCommand.{ HintString, HintDocument }

    def writes(hint: CountCommand.Hint): JsValue = hint match {
      case HintString(s)     => JsString(s)
      case HintDocument(obj) => obj
    }
  }

  implicit object CountWriter
      extends pack.Writer[ResolvedCollectionCommand[CountCommand.Count]] {

    def writes(count: ResolvedCollectionCommand[CountCommand.Count]): pack.Document = {
      val fields = Seq[Option[(String, Json.JsValueWrapper)]](
        Some("count" -> count.collection),
        count.command.query.map("query" -> _),
        option(count.command.limit != 0, count.command.limit).map("limit" -> _),
        option(count.command.skip != 0, count.command.skip).map("skip" -> _),
        count.command.hint.map("hint" -> _)
      ).flatten

      Json.obj(fields: _*)
    }
  }

  implicit object CountResultReader
      extends pack.Reader[CountCommand.CountResult] {
    def reads(js: JsValue): JsResult[CountCommand.CountResult] =
      (js \ "n").validate[Int].map(CountCommand.CountResult(_))
  }

  object JSONInsertCommand extends IC[JSONSerializationPack.type] {
    val pack = commands.pack
  }
  val InsertCommand = JSONInsertCommand
  type ResolvedInsert = ResolvedCollectionCommand[InsertCommand.Insert]

  implicit object WriteConcernWriter extends pack.Writer[WriteConcern] {
    def writes(wc: WriteConcern): pack.Document = {
      val obj = Json.obj(
        "w" -> ((wc.w match {
          case GLE.Majority                 => JsString("majority")
          case GLE.TagSet(tagSet)           => JsString(tagSet)
          case GLE.WaitForAknowledgments(n) => JsNumber(n)
        }): JsValue),
        "wtimeout" -> wc.wtimeout
      )

      if (!wc.j) obj else obj + ("j" -> JsBoolean(true))
    }
  }

  implicit object InsertWriter extends pack.Writer[ResolvedInsert] {
    def writes(cmd: ResolvedInsert): pack.Document = Json.obj(
      "insert" -> cmd.collection,
      "documents" -> cmd.command.documents,
      "ordered" -> cmd.command.ordered,
      "writeConcern" -> cmd.command.writeConcern
    )
  }

  object JSONUpdateCommand extends UC[JSONSerializationPack.type] {
    val pack = commands.pack
  }

  val UpdateCommand = JSONUpdateCommand
  type ResolvedUpdate = ResolvedCollectionCommand[UpdateCommand.Update]

  implicit object UpdateElementWriter
      extends pack.Writer[UpdateCommand.UpdateElement] {

    def writes(element: UpdateCommand.UpdateElement): pack.Document = Json.obj(
      "q" -> element.q,
      "u" -> element.u,
      "upsert" -> element.upsert,
      "multi" -> element.multi
    )
  }

  implicit object UpdateWriter extends pack.Writer[ResolvedUpdate] {
    def writes(cmd: ResolvedUpdate): pack.Document = Json.obj(
      "update" -> cmd.collection,
      "updates" -> Json.toJson(cmd.command.documents),
      "ordered" -> cmd.command.ordered,
      "writeConcern" -> cmd.command.writeConcern
    )
  }

  implicit object UpsertedReader extends pack.Reader[Upserted] {
    def reads(js: JsValue): JsResult[Upserted] = for {
      ix <- (js \ "index").validate[Int]
      id <- (js \ "_id").validate[JsValue].flatMap(BSONFormats.toBSON)
    } yield Upserted(index = ix, _id = id)
  }

  implicit object WriteErrorReader extends pack.Reader[WriteError] {
    def reads(js: JsValue): JsResult[WriteError] = for {
      id <- (js \ "index").validate[Int]
      co <- (js \ "code").validate[Int]
      em <- (js \ "errmsg").validate[String]
    } yield WriteError(index = id, code = co, errmsg = em)
  }

  implicit object WriteConcernErrorReader
      extends pack.Reader[WriteConcernError] {
    def reads(js: JsValue): JsResult[WriteConcernError] = for {
      co <- (js \ "code").validate[Int]
      em <- (js \ "errmsg").validate[String]
    } yield WriteConcernError(code = co, errmsg = em)
  }

  implicit object UpdateReader extends pack.Reader[UpdateCommand.UpdateResult] {
    def reads(js: JsValue): JsResult[UpdateCommand.UpdateResult] = for {
      ok <- readOpt[Int](js \ "ok")
      n <- readOpt[Int](js \ "n")
      mo <- readOpt[Int](js \ "nModified")
      up <- readOpt[Seq[Upserted]](js \ "upserted")
      we <- readOpt[Seq[WriteError]](js \ "writeErrors")
      ce <- readOpt[WriteConcernError](js \ "writeConcernError")
      co <- readOpt[Int](js \ "code") //FIXME There is no corresponding official docs.
      em <- readOpt[String](js \ "errmsg") //FIXME There is no corresponding official docs.
    } yield UpdateWriteResult(
      ok = ok.exists(_ != 0),
      n = n.getOrElse(0),
      nModified = mo.getOrElse(0),
      upserted = up.getOrElse(Seq.empty[Upserted]),
      writeErrors = we.getOrElse(Seq.empty[WriteError]),
      writeConcernError = ce,
      code = co,
      errmsg = em
    )
  }

  object JSONDeleteCommand extends DC[JSONSerializationPack.type] {
    val pack = commands.pack
  }
  val DeleteCommand = JSONDeleteCommand
  type ResolvedDelete = ResolvedCollectionCommand[DeleteCommand.Delete]

  implicit object DeleteElementWriter
      extends pack.Writer[DeleteCommand.DeleteElement] {
    def writes(e: DeleteCommand.DeleteElement): pack.Document = Json.obj(
      "q" -> e.q, "limit" -> e.limit
    )
  }

  implicit object DeleteWriter extends pack.Writer[ResolvedDelete] {
    def writes(cmd: ResolvedDelete): pack.Document = Json.obj(
      "delete" -> cmd.collection,
      "deletes" -> Json.toJson(cmd.command.deletes),
      "ordered" -> cmd.command.ordered,
      "writeConcern" -> cmd.command.writeConcern
    )
  }

  implicit object DefaultWriteResultReader
      extends pack.Reader[DefaultWriteResult] {
    def reads(js: JsValue): JsResult[DefaultWriteResult] = for {
      ok <- readOpt[Int](js \ "ok")
      n <- readOpt[Int](js \ "n")
      we <- readOpt[Seq[WriteError]](js \ "writeErrors")
      ce <- readOpt[WriteConcernError](js \ "writeConcernError")
      co <- readOpt[Int](js \ "code") //FIXME There is no corresponding official docs.      
      em <- readOpt[String](js \ "errmsg") //FIXME There is no corresponding official docs.
    } yield DefaultWriteResult(
      ok = ok.exists(_ != 0),
      n = n.getOrElse(0),
      writeErrors = we.getOrElse(Seq.empty[WriteError]),
      writeConcernError = ce,
      code = co,
      errmsg = em
    )
  }

  implicit object LastErrorReader extends pack.Reader[LastError] {
    def reads(js: JsValue): JsResult[LastError] = for {
      ok <- readOpt[Int](js \ "ok")
      er <- readOpt[String](js \ "err")
      co <- readOpt[Int](js \ "code")
      lo <- readOpt[Long](js \ "lastOp")
      n <- readOpt[Int](js \ "n")
      ss <- readOpt[String](js \ "singleShard")
      ux <- readOpt[Boolean](js \ "updatedExisting")
      ue <- readOpt[JsValue](js \ "upserted").flatMap(
        _.fold[JsResult[Option[BSONValue]]](
          JsSuccess(None)
        )(BSONFormats.toBSON(_).map(Some(_)))
      )
      wn <- (js \ "wnote").get match {
        case JsString("majority") => JsSuccess(Some(GLE.Majority))
        case JsString(tagSet)     => JsSuccess(Some(GLE.TagSet(tagSet)))
        case JsNumber(acks) => JsSuccess(
          Some(GLE.WaitForAknowledgments(acks.toInt))
        )
        case _ => JsSuccess(Option.empty[GLE.W])
      }
      wt <- readOpt[Boolean](js \ "wtimeout")
      we <- readOpt[Int](js \ "waited")
      wm <- readOpt[Int](js \ "wtime")
    } yield LastError(ok.exists(_ != 0), er, co, lo, n.getOrElse(0),
      ss, ux.getOrElse(false), ue, wn, wt.getOrElse(false), we, wm)
  }

  import reactivemongo.play.json.commands.{
    JSONFindAndModifyCommand,
    JSONFindAndModifyImplicits
  }
  val FindAndModifyCommand = JSONFindAndModifyCommand
  implicit def FindAndModifyWriter = JSONFindAndModifyImplicits.FindAndModifyWriter
  implicit def FindAndModifyReader = JSONFindAndModifyImplicits.FindAndModifyResultReader

  import reactivemongo.play.json.commands.{
    JSONAggregationFramework,
    JSONAggregationImplicits
  }
  val AggregationFramework = JSONAggregationFramework
  implicit def AggregateWriter = JSONAggregationImplicits.AggregateWriter
  implicit def AggregateReader =
    JSONAggregationImplicits.AggregationResultReader

}

/**
 * A Collection that interacts with the Play JSON library, using `Reads` and `Writes`.
 */
case class JSONCollection(
  db: DB, name: String, failoverStrategy: FailoverStrategy
)
    extends GenericCollection[JSONSerializationPack.type] with CollectionMetaCommands {

  import reactivemongo.core.commands.GetLastError

  val pack = JSONSerializationPack
  val BatchCommands = JSONBatchCommands

  def genericQueryBuilder: GenericQueryBuilder[JSONSerializationPack.type] =
    JSONQueryBuilder(this, failoverStrategy)

  /**
   * Inserts the document, or updates it if it already exists in the collection.
   *
   * @param doc The document to save.
   */
  @deprecated("0.11.1", "Use [[update]] with `upsert` set to true")
  def save(doc: JsObject)(implicit ec: ExecutionContext): Future[WriteResult] =
    save(doc, WriteConcern.Default)

  /**
   * Inserts the document, or updates it if it already exists in the collection.
   *
   * @param doc The document to save.
   * @param writeConcern The write concern
   */
  @deprecated("0.11.1", "Use [[update]] with `upsert` set to true")
  def save(doc: pack.Document, writeConcern: WriteConcern)(implicit ec: ExecutionContext): Future[WriteResult] = {
    import reactivemongo.bson.BSONObjectID
    (doc \ "_id").toOption match {
      case None => insert(
        doc + ("_id" ->
          BSONFormats.BSONObjectIDFormat.writes(BSONObjectID.generate)),
        writeConcern
      )

      case Some(id) =>
        update(Json.obj("_id" -> id), doc, writeConcern, upsert = true)
    }
  }

  /**
   * Inserts the document, or updates it if it already exists in the collection.
   *
   * @param doc The document to save.
   * @param writeConcern The write concern
   */
  @deprecated("0.11.1", "Use [[update]] with `upsert` set to true")
  def save[T](doc: T, writeConcern: WriteConcern = WriteConcern.Default)(implicit ec: ExecutionContext, writer: Writes[T]): Future[WriteResult] =
    writer.writes(doc) match {
      case d @ JsObject(_) => save(d, writeConcern)
      case _ =>
        Future.failed[WriteResult](new Exception("cannot write object"))
    }
}

case class JSONQueryBuilder(
  collection: Collection,
  failover: FailoverStrategy,
  queryOption: Option[JsObject] = None,
  sortOption: Option[JsObject] = None,
  projectionOption: Option[JsObject] = None,
  hintOption: Option[JsObject] = None,
  explainFlag: Boolean = false,
  snapshotFlag: Boolean = false,
  commentString: Option[String] = None,
  options: QueryOpts = QueryOpts(),
  maxTimeMsOption: Option[Long] = None
)
    extends GenericQueryBuilder[JSONSerializationPack.type] {

  import play.api.libs.json.{ JsValue, Json }

  type Self = JSONQueryBuilder

  val pack = JSONSerializationPack
  private def empty = Json.obj()

  def copy(queryOption: Option[JsObject], sortOption: Option[JsObject], projectionOption: Option[JsObject], hintOption: Option[JsObject], explainFlag: Boolean, snapshotFlag: Boolean, commentString: Option[String], options: QueryOpts, failover: FailoverStrategy, maxTimeMsOption: Option[Long]): JSONQueryBuilder =
    JSONQueryBuilder(collection, failover, queryOption, sortOption, projectionOption, hintOption, explainFlag, snapshotFlag, commentString, options, maxTimeMsOption)

  def merge(readPreference: ReadPreference): JsObject = {
    def pref = {
      val mode = readPreference match {
        case ReadPreference.Primary                    => "primary"
        case ReadPreference.PrimaryPreferred(filter)   => "primaryPreferred"
        case ReadPreference.Secondary(filter)          => "secondary"
        case ReadPreference.SecondaryPreferred(filter) => "secondaryPreferred"
        case ReadPreference.Nearest(filter)            => "nearest"
      }
      val base = Seq[(String, JsValue)]("mode" -> Json.toJson(mode))

      JsObject(readPreference match {
        case ReadPreference.Taggable(tagSet) => base :+ ("tags" -> JsArray(
          tagSet.map(tags => JsObject(tags.toList.map {
            case (k, v) => k -> Json.toJson(v)
          }))
        ))

        case _ => base
      })
    }

    val optional = List[Option[(String, JsValue)]](
      queryOption.map { "$query" -> Json.toJson(_) },
      sortOption.map { "$orderby" -> Json.toJson(_) },
      hintOption.map { "$hint" -> Json.toJson(_) },
      maxTimeMsOption.map { "$maxTimeMS" -> Json.toJson(_) },
      commentString.map { "$comment" -> Json.toJson(_) },
      option(explainFlag, "$explain" -> Json.toJson(true)),
      option(snapshotFlag, "$snapshot" -> Json.toJson(true))
    ).flatten

    JsObject((optional :+ ("$readPreference" -> pref)))
  }
}

// JSON extension for cursors

import reactivemongo.api.{
  Cursor,
  CursorProducer,
  FlattenedCursor,
  WrappedCursor
}

sealed trait JsCursor[T] extends Cursor[T] {
  /**
   * Returns the result of cursor as a JSON array.
   *
   * @param maxDocs Maximum number of documents to be retrieved
   */
  def jsArray(maxDocs: Int = Int.MaxValue)(implicit ec: ExecutionContext): Future[JsArray]

}

class JsCursorImpl[T: Writes](val wrappee: Cursor[T])
    extends JsCursor[T] with WrappedCursor[T] {
  import Cursor.{ Cont, Fail }

  private val writes = implicitly[Writes[T]]

  def jsArray(maxDocs: Int = Int.MaxValue)(implicit ec: ExecutionContext): Future[JsArray] = wrappee.foldWhile(Json.arr(), maxDocs)(
    (arr, res) => Cont(arr :+ writes.writes(res)),
    (_, error) => Fail(error)
  )

}

class JsFlattenedCursor[T](val future: Future[JsCursor[T]])
    extends FlattenedCursor[T](future) with JsCursor[T] {

  def jsArray(maxDocs: Int = Int.MaxValue)(implicit ec: ExecutionContext): Future[JsArray] = future.flatMap(_.jsArray(maxDocs))

}

/** Implicits of the JSON extensions for cursors. */
object JsCursor {
  import reactivemongo.api.{ CursorFlattener, CursorProducer }

  /** Provides JSON instances for CursorProducer typeclass. */
  implicit def cursorProducer[T: Writes] = new CursorProducer[T] {
    type ProducedCursor = JsCursor[T]

    // Returns a cursor with JSON operations.
    def produce(base: Cursor[T]): JsCursor[T] = new JsCursorImpl[T](base)
  }

  /** Provides flattener for JSON cursor. */
  implicit object cursorFlattener extends CursorFlattener[JsCursor] {
    def flatten[T](future: Future[JsCursor[T]]): JsCursor[T] =
      new JsFlattenedCursor(future)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy