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

okapies.finagle.kafka.protocol.Spec.scala Maven / Gradle / Ivy

package okapies.finagle.kafka.protocol

import java.nio.charset.Charset

import scala.annotation.tailrec
import scala.collection.mutable.ArrayBuffer

import org.jboss.netty.buffer.ChannelBuffer

import _root_.kafka.common.KafkaException
import _root_.kafka.message.InvalidMessageException

private[protocol] object Spec {

  // Type aliases
  type Int8 = Byte
  type Int16 = Short
  type Int32 = Int
  type Int64 = Long

  // Api Versions
  final val ApiVersion0 = 0: Int16

  // Api Keys
  final val ApiKeyProduce          = 0: Int16
  final val ApiKeyFetch            = 1: Int16
  final val ApiKeyOffset           = 2: Int16
  final val ApiKeyMetadata         = 3: Int16
  final val ApiKeyLeaderAndIsr     = 4: Int16
  final val ApiKeyStopReplica      = 5: Int16
  final val ApiKeyOffsetCommit     = 8: Int16
  final val ApiKeyOffsetFetch      = 9: Int16
  final val ApiKeyConsumerMetadata = 10: Int16

  // length of fields
  final val CorrelationIdLength = 4
  final val ArraySizeLength = 4

  final val OffsetLength = 8
  final val MessageSizeLength = 4

  // Encoding
  final val DefaultCharset = Charset.forName("UTF-8")

  /*
   * Implicit conversions
   */

  import scala.language.implicitConversions

  /**
   * The wrapper of ChannelBuffer that provides helper methods for Kafka protocol.
   */
  private[protocol] implicit class KafkaChannelBuffer(val buf: ChannelBuffer) extends AnyVal {

    /*
     * Methods for encoding value and writing into buffer.
     */

    /**
     * A simple wrapper for [[org.jboss.netty.buffer.ChannelBuffer#writeByte]].
     */
    @inline
    def encodeInt8(integer: Int8) = buf.writeByte(integer.asInstanceOf[Int])

    /**
     * A simple wrapper for [[org.jboss.netty.buffer.ChannelBuffer#writeShort]].
     */
    @inline
    def encodeInt16(integer: Int16) = buf.writeShort(integer.asInstanceOf[Int])

    /**
     * A simple wrapper for [[org.jboss.netty.buffer.ChannelBuffer#writeInt]].
     */
    @inline
    def encodeInt32(integer: Int32) = buf.writeInt(integer)

    /**
     * A simple wrapper for [[org.jboss.netty.buffer.ChannelBuffer#writeLong]].
     */
    @inline
    def encodeInt64(integer: Int64) = buf.writeLong(integer)

    /**
     * Write the buffer's data with prefixed length field. The number of the
     * transferred bytes is (4 + bytes.readableBytes).
     */
    @inline
    def encodeBytes(bytes: ChannelBuffer) {
      if (bytes != null) {
        val n: Int32 = bytes.readableBytes
        buf.encodeInt32(n)          // int32 (length field)
        buf.writeBytes(bytes, 0, n) // content
      } else {
        buf.encodeInt32(-1:Int32) // indicates null
      }
    }

    /**
     * Write the array's data with prefixed length field. The number of the
     * transferred bytes is (4 + bytes.length).
     */
    @inline
    def encodeBytes(bytes: Array[Byte]) {
      if (bytes != null) {
        val n: Int32 = bytes.length
        buf.encodeInt32(n)          // int32 (length field)
        buf.writeBytes(bytes, 0, n) // content
      } else {
        buf.encodeInt32(-1:Int32) // indicates null
      }
    }

    /**
     * Write the string as UTF-8 decoded bytes, with prefixed length field.
     * The number of the transferred bytes is (2 + str.getBytes(UTF-8).length).
     */
    @inline
    def encodeString(str: String) {
      if (str != null) {
        val bytes = str.getBytes(DefaultCharset)
        val n = bytes.length
        if ( n <= Short.MaxValue) {
          buf.encodeInt16(n.asInstanceOf[Int16]) // int16 (length field)
          buf.writeBytes(bytes, 0, n)            // content
        } else {
          throw new KafkaException("Size of string exceeds " + Short.MaxValue + ".")
        }
      } else {
        buf.encodeInt16(-1:Int16) // indicates null
      }
    }

    /**
     * Write the array with prefixed length field.
     */
    @inline
    def encodeArray[A](array: Seq[A])(f: A => Unit) {
      if (array != null) {
        buf.encodeInt32(array.length) // N => int32  (length field)
        array.foreach(f)              // write each element
      } else {
        buf.encodeInt32(-1:Int32) // what is null indicator?
      }
    }

    /**
     * Write the string array with prefixed length field.
     */
    @inline
    def encodeStringArray(array: Seq[String]) = encodeArray(array)(buf.encodeString)

    /**
     * Write the map as array with prefixed length field.
     */
    @inline
    def encodeArray[A, B](array: Map[A, B])(f: ((A, B)) => Unit) {
      if (array != null) {
        buf.encodeInt32(array.size) // N => int32  (length field)
        array.foreach(f)            // write each element
      } else {
        buf.encodeInt32(-1:Int32) // what is null indicator?
      }
    }

    /**
     * Write the MessageSet. MessageSet is NOT preceded by an int32 like other array elements.
     */
    @inline
    def encodeMessageSet(messages: Seq[Message])(f: Message => Unit) = {
      buf.encodeInt32(messages.foldLeft(0)(_ + OffsetLength + MessageSizeLength + _.size))
      messages.foreach(f)
    }

    @inline
    def encodeMessageSetWithOffset(messages: Seq[MessageWithOffset])(f: MessageWithOffset => Unit) = {
      buf.encodeInt32(messages.foldLeft(0)(_ + OffsetLength + MessageSizeLength + _.message.size))
      messages.foreach(f)
    }


    /*
     * Methods for decoding next bytes.
     */

    @inline
    def decodeInt8(): Int8 = buf.readByte()

    @inline
    def decodeInt16(): Int16 = buf.readShort()

    @inline
    def decodeInt32(): Int32 = buf.readInt()

    @inline
    def decodeInt64(): Int64 = buf.readLong()

    @inline
    def decodeBytes(): ChannelBuffer = {
      val n = buf.decodeInt32() // N => int32
      buf.readBytes(n)          // content
    }

    @inline
    def decodeString(): String = {
      val n = buf.decodeInt16()    // N => int16
      val bytes = buf.readBytes(n) // content

      val (array, offset) =
        if (bytes.hasArray) {
          (bytes.array, bytes.arrayOffset)
        } else {
          val _array = new Array[Byte](n)
          bytes.readBytes(_array)
          (_array, 0)
        }

      new String(array, offset, n, DefaultCharset)
    }

    @inline
    def decodeArray[A](f: => A): Seq[A] = {
      val n: Int32 = buf.decodeInt32() // N => int32
      (0 until n).map(_ => f)
    }

    @inline
    def decodeStringArray() = decodeArray(buf.decodeString())

    @inline
    def decodeMessageSet(): Seq[MessageWithOffset] = {
      @tailrec
      def decodeMessages(msgSetBuf: ChannelBuffer,
                         msgSet: ArrayBuffer[MessageWithOffset]): Seq[MessageWithOffset] = {
        if (msgSetBuf.readableBytes < 12) {    // 12 = length(Offset+MessageSize)
          msgSet
        } else {
          val offset = msgSetBuf.decodeInt64() // Offset => int64
          val size = msgSetBuf.decodeInt32()   // MessageSize => int32
          if (size < Message.MinHeaderSize) {
            throw new InvalidMessageException(s"Message size is corrupted: $size")
          }

          if (msgSetBuf.readableBytes < size) {
            // ignore a partial message at the end of the message set
            msgSet
          } else {
            val bytes = msgSetBuf.readBytes(size) // Message
            msgSet.append(MessageWithOffset(offset, Message(bytes)))

            decodeMessages(msgSetBuf, msgSet)
          }
        }
      }

      val size = buf.decodeInt32()        // MessageSetSize => int32
      val msgSetBuf = buf.readBytes(size) // MessageSet

      decodeMessages(msgSetBuf, ArrayBuffer[MessageWithOffset]())
    }

  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy