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

io.buoyant.k8s.Json.scala Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
package io.buoyant.k8s

import com.fasterxml.jackson.core.{JsonParser, JsonProcessingException}
import com.fasterxml.jackson.databind._
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.fasterxml.jackson.module.scala.experimental.ScalaObjectMapper
import com.twitter.concurrent.AsyncStream
import com.twitter.finagle.util.LoadService
import com.twitter.io.{Buf, Reader}
import com.twitter.logging.Logger
import com.twitter.util.{Future, Return, Throw, Try}
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.reflect.classTag

object Json {

  private[this] val log = Logger.get("k8s")

  private[this] val mapper = new ObjectMapper with ScalaObjectMapper
  mapper.registerModule(DefaultScalaModule)
  mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
  LoadService[SerializationModule].foreach { svc => mapper.registerModule(svc.module) }

  def read[T: Manifest](buf: Buf): Try[T] = {
    val Buf.ByteArray.Owned(bytes, begin, end) = Buf.ByteArray.coerce(buf)
    Try(mapper.readValue[T](bytes, begin, end - begin))
  }

  def writeBuf[T: Manifest](t: T) = Buf.ByteArray.Owned(mapper.writeValueAsBytes(t))

  /*
   * JSON Streaming
   */

  object EndOfStream extends Throwable

  private[this] object Incomplete {
    val unexpectedEOI = "Unexpected end-of-input"
    def unapply(jpe: JsonProcessingException): Boolean =
      jpe.getMessage match {
        case null => false
        case msg => msg.startsWith(unexpectedEOI)
      }
  }

  protected[this] def parse[T](buf: Buf)(f: JsonParser => T) = {
    val Buf.ByteArray.Owned(bytes, begin, end) = Buf.ByteArray.coerce(buf)
    val parser = mapper.getFactory.createParser(bytes, begin, end - begin)
    try f(parser) finally parser.close()
  }

  /**
   * Given a chunk of bytes, read a stream of objects, and return the remaining unread buffer.
   */
  def readChunked[T: Manifest](chunk: Buf): (Seq[T], Buf) = {
    var objs = mutable.Buffer.empty[T]
    var offset = 0L
    parse(chunk) { json =>
      var reading = true
      while (reading) {
        log.ifTrace {
          val Buf.Utf8(rest) = chunk.slice(offset.toInt, chunk.length)
          s"json chunk reading: [$offset, ${chunk.length}] $rest"
        }

        try {
          json.readValueAs(classTag[T].runtimeClass) match {
            case obj: T if obj != null =>
              objs.append(obj)

              val prior = offset
              offset = json.getCurrentLocation.getByteOffset
              reading = offset < chunk.length - 1

              log.ifTrace {
                val Buf.Utf8(read) = chunk.slice(prior.toInt, offset.toInt)
                s"json chunk read: [$prior, $offset] $read $obj"
              }

            case _ =>
              val Buf.Utf8(chunkstr) = chunk
              val msg = s"could not decode json object in chunk @ ${offset} bytes: ${chunkstr}"
              throw new IllegalStateException(msg)
          }
        } catch {
          case Incomplete() =>
            reading = false
            log.ifTrace {
              val Buf.Utf8(incomplete) = chunk.slice(offset.toInt, chunk.length)
              s"json chunk incomplete: [$offset, ${chunk.length}] $incomplete"
            }
        }
      }
    }

    val rest =
      if (offset >= chunk.length) Buf.Empty
      else chunk.slice(offset.toInt, chunk.length)

    (objs, rest)
  }

  def readStream[T: Manifest](reader: Reader, bufsize: Int = 8 * 1024): AsyncStream[T] = {
    def chunks(init: Buf): AsyncStream[T] =
      for {
        chunk <- {
          log.trace("json reading chunk of %d bytes", bufsize)
          val read = reader.read(bufsize)
          read.respond {
            case Return(Some(Buf.Utf8(chunk))) =>
              log.trace("json read chunk: %s", chunk)
            case Return(None) | Throw(_: Reader.ReaderDiscarded) =>
              log.trace("json read eoc")
            case Throw(e) =>
              log.warning(e, "json read error")
          }
          AsyncStream.fromFuture(read).flatMap(AsyncStream.fromOption)
        }

        item <- {
          val (items, tail) = readChunked[T](init.concat(chunk))
          AsyncStream.fromSeq(items).concat(chunks(tail))
        }
      } yield item

    chunks(Buf.Empty)
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy