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

com.twitter.finatra.http.internal.marshalling.CallbackConverter.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finatra.http.internal.marshalling

import com.twitter.concurrent.AsyncStream
import com.twitter.finagle.http._
import com.twitter.finatra.http.response.{ResponseBuilder, StreamingResponse}
import com.twitter.finatra.json.FinatraObjectMapper
import com.twitter.finatra.json.internal.streaming.JsonStreamParser
import com.twitter.util.Future
import javax.inject.Inject

class CallbackConverter @Inject()(
  messageBodyManager: MessageBodyManager,
  responseBuilder: ResponseBuilder,
  mapper: FinatraObjectMapper,
  jsonStreamParser: JsonStreamParser) {

  /* Public */

  // Note we use Manifest here as we support Scala 2.10 and reflection is not thread-safe in 2.10 (precluding the use
  // of typeTag and/or classTag). See: http://docs.scala-lang.org/overviews/reflection/thread-safety.html
  def convertToFutureResponse[RequestType: Manifest, ResponseType: Manifest](callback: RequestType => ResponseType): Request => Future[Response] = {
    val requestConvertedCallback: (Request => ResponseType) = createRequestCallback[RequestType, ResponseType](callback)
    createResponseCallback[ResponseType](requestConvertedCallback)
  }

  /* Private */

  private def createRequestCallback[RequestType: Manifest, ResponseType: Manifest](callback: RequestType => ResponseType): Request => ResponseType = {
    if (manifest[RequestType] == manifest[Request]) {
      callback.asInstanceOf[(Request => ResponseType)]
    }
    else if (runtimeClassEq[RequestType, AsyncStream[_]]) {
      val asyncStreamTypeParam = manifest[RequestType].typeArguments.head
      request: Request =>
        val asyncStream = jsonStreamParser.parseArray(request.reader)(asyncStreamTypeParam)
        callback(asyncStream.asInstanceOf[RequestType])
    }
    else if (runtimeClassEq[RequestType, Int]) {
      // NOTE: "empty" route callbacks that return a String are inferred as a RequestType of
      // Int by Scala because StringOps.apply is a function of Int => Char.
      throw new Exception(
        "Routes with empty (with no parameter) route callbacks or route callbacks with a parameter of type Int are not allowed. " +
          "Please specify a parameter in your route callback of the appropriate type.")
    }
    else {
      request: Request =>
        val callbackInput = messageBodyManager.read[RequestType](request)
        callback(callbackInput)
    }
  }

  private def createResponseCallback[ResponseType: Manifest](requestCallback: (Request) => ResponseType): (Request) => Future[Response] = {
    if (manifest[ResponseType] == manifest[Future[Response]]) {
      requestCallback.asInstanceOf[(Request => Future[Response])]
    }
    else if (isFutureOption[ResponseType]) {
      request: Request =>
        requestCallback(request).asInstanceOf[Future[Option[_]]] map optionToHttpResponse(request)
    }
    else if (runtimeClassEq[ResponseType, AsyncStream[_]]) {
      request: Request =>
        val asyncStream = requestCallback(request).asInstanceOf[AsyncStream[_]]

        val streamingResponse = StreamingResponse.jsonArray(
          toBuf = mapper.writeValueAsBuf,
          asyncStream = asyncStream)

        streamingResponse.toFutureFinagleResponse
    }
    else if (runtimeClassEq[ResponseType, Future[_]]) {
      request: Request =>
        requestCallback(request).asInstanceOf[Future[_]] map createHttpResponse(request)
    }
    else if (runtimeClassEq[ResponseType, StreamingResponse[_]]) {
      request: Request =>
        requestCallback(request).asInstanceOf[StreamingResponse[_]].toFutureFinagleResponse
    }
    else if (manifest[ResponseType] == manifest[Response]) {
      request: Request =>
        Future(
          requestCallback(request).asInstanceOf[Response])
    }
    else if (runtimeClassEq[ResponseType, Option[_]]) {
      request: Request =>
        Future(
          optionToHttpResponse(request)(requestCallback(request).asInstanceOf[Option[_]]))
    }
    else if (runtimeClassEq[ResponseType, String]) {
      // optimized
      request: Request =>
        val response = Response(Version.Http11, Status.Ok)
        response.setContentString(requestCallback(request).asInstanceOf[String])
        response.headerMap.add(Fields.ContentType, ResponseBuilder.PlainTextContentType)
        Future.value(response)
    }
    else {
      request: Request =>
        Future(
          createHttpResponse(request)(requestCallback(request)))
    }
  }

  private def optionToHttpResponse(request: Request)(response: Option[_]): Response = {
    response map createHttpResponse(request) getOrElse {
      responseBuilder.notFound("")
    }
  }

  private def createHttpResponse(request: Request)(any: Any): Response = {
    any match {
      case response: Response => response
      case _ => responseBuilder.ok(request, any)
    }
  }

  private def runtimeClassEq[T: Manifest, U: Manifest]: Boolean = {
    manifest[T].runtimeClass == manifest[U].runtimeClass
  }

  private def isFutureOption[T: Manifest]: Boolean = {
    val typeArgs = manifest[T].typeArguments
    runtimeClassEq[T, Future[_]] &&
      typeArgs.size == 1 &&
      typeArgs.head.runtimeClass == classOf[Option[_]]
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy