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

core.protocol.Response.scala Maven / Gradle / Ivy

package reactivemongo.core.protocol

import scala.util.{ Failure, Success }

import scala.concurrent.{ ExecutionContext, Future }

import reactivemongo.io.netty.buffer.{ ByteBuf, Unpooled }

import reactivemongo.core.errors.{ DatabaseException, GenericDriverException }

import reactivemongo.api.bson.collection.BSONSerializationPack

/**
 * A Mongo Wire Protocol Response messages.
 *
 * @param header the header of this response
 * @param reply the reply operation contained in this response
 * @param documents the body of this response, a [[http://static.netty.io/3.5/api/org/jboss/netty/buffer/ByteBuf.html ByteBuf]] containing 0, 1, or many documents
 * @param info some meta information about this response
 */
private[reactivemongo] sealed abstract class Response(
    val header: MessageHeader,
    val reply: Reply,
    val documents: ByteBuf,
    val info: ResponseInfo) {

  /** If this response is in error, explain this error. */
  lazy val error: Option[DatabaseException] = {
    if (reply.inError) {
      val bson = Response.parse(this)

      if (bson.hasNext) {
        Some(DatabaseException(BSONSerializationPack)(bson.next()))
      } else None
    } else None
  }

  private[reactivemongo] def cursorID(id: Long): Response

  private[reactivemongo] def startingFrom(offset: Int): Response

  override def toString = s"Response($header, $reply, $info)"
}

private[reactivemongo] object Response {
  import reactivemongo.api.SerializationPack
  import reactivemongo.api.bson.BSONDocument

  def apply(
      header: MessageHeader,
      reply: Reply,
      documents: ByteBuf,
      info: ResponseInfo
    ): Response = Successful(header, reply, documents, info)

  @inline def parse(response: Response): Iterator[BSONDocument] =
    parse(BSONSerializationPack)(response)

  def parse[P <: SerializationPack](
      pack: P
    )(response: Response
    ): Iterator[pack.Document] =
    ReplyDocumentIterator.parse(pack)(response)(pack.IdentityReader)

  def preload(
      response: Response
    )(implicit
      ec: ExecutionContext
    ): Future[(Response, BSONDocument)] =
    response match {
      case r @ WithCursor(_, _, _, _, _, cursorDoc, _) =>
        Future.successful(r -> cursorDoc)

      case CommandError(_, _, _, cause) =>
        Future.failed(cause)

      case Successful(_, Reply(_, _, _, 0), _, _) =>
        Future.failed(
          new GenericDriverException(
            s"Cannot preload empty response: $response"
          )
        )

      case Successful(header, reply, docs, info) => {
        val buf = docs.duplicate()

        ResponseDecoder.first(buf) match {
          case Success(first) =>
            Future {
              buf.resetReaderIndex()

              val other = Successful(header, reply, buf, info)
              other.first = Option(first)

              other -> first
            }

          case Failure(cause) => Future.failed(cause)
        }
      }
    }

  // ---

  private[reactivemongo] final case class Successful(
      _header: MessageHeader,
      _reply: Reply,
      _documents: ByteBuf,
      _info: ResponseInfo)
      extends Response(_header, _reply, _documents, _info) {

    @volatile private[reactivemongo] var first = Option.empty[BSONDocument]

    private[reactivemongo] def cursorID(id: Long): Response =
      copy(_reply = this._reply.copy(cursorID = id))

    private[reactivemongo] def startingFrom(offset: Int): Response =
      copy(_reply = this._reply.copy(startingFrom = offset))
  }

  // For MongoDB 3.2+ response with cursor
  private[reactivemongo] final case class WithCursor(
      _header: MessageHeader,
      _reply: Reply,
      _documents: ByteBuf,
      _info: ResponseInfo,
      ns: String,
      private[core] val cursor: BSONDocument,
      private[core] val preloaded: Seq[BSONDocument])
      extends Response(_header, _reply, _documents, _info) {

    private[reactivemongo] def cursorID(id: Long): Response =
      copy(_reply = this._reply.copy(cursorID = id))

    private[reactivemongo] def startingFrom(offset: Int): Response =
      copy(_reply = this._reply.copy(startingFrom = offset))
  }

  private[reactivemongo] final case class CommandError(
      _header: MessageHeader,
      _reply: Reply,
      _info: ResponseInfo,
      private[reactivemongo] val cause: DatabaseException)
      extends Response(_header, _reply, Unpooled.EMPTY_BUFFER, _info) {
    override lazy val error: Option[DatabaseException] = Some(cause)

    private[reactivemongo] def cursorID(id: Long): Response =
      copy(_reply = this._reply.copy(cursorID = id))

    private[reactivemongo] def startingFrom(offset: Int): Response =
      copy(_reply = this._reply.copy(startingFrom = offset))
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy