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

com.thoughtworks.microbuilder.play.RpcController.scala Maven / Gradle / Ivy

The newest version!
package com.thoughtworks.microbuilder.play

import java.io.ByteArrayOutputStream

import com.thoughtworks.microbuilder.core.{IRouteConfiguration, MatchResult}
import com.thoughtworks.microbuilder.play.exception.MicrobuilderException.{WrongResponseFormatException, NativeException}
import haxe.io.Output
import haxe.lang.HaxeException
import jsonStream.io.PrettyTextPrinter
import jsonStream.rpc.{IJsonResponseHandler, IJsonService}
import jsonStream.{JsonStream, JsonStreamPair}
import org.slf4j.LoggerFactory
import play.api.http.Writeable
import play.api.mvc._
import play.api.http.HeaderNames

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Future, Promise}
import scala.util.Try

private object RpcController {
  private val logger = LoggerFactory.getLogger(classOf[RpcController])
}

class RpcController(rpcEntries: Seq[RpcEntry]) extends Controller {

  import RpcController._

  def rpc(uri: String) = Action.async { request =>
    val uriWithQuery =
      raw"""$uri${
        request.uri.indexOf('?') match {
          case -1 => ""
          case i => request.uri.substring(i)
        }
      }"""
    logger.debug(raw"""Parsing URI $uriWithQuery...""")

    val bodyJsonStream: Option[JsonStream] = request.body.asText match {
      case None => None
      case Some(jsonStream) => Some(JsonStream.STRING(jsonStream))
    }

    val matchedEntries = for {
      rpcEntry <- rpcEntries.iterator
      matchResult = rpcEntry.routeConfiguration.matchUri(
        new com.thoughtworks.microbuilder.core.Request(
          request.method,
          uriWithQuery,
          (for {
            header <- request.headers.headers
          } yield new com.thoughtworks.microbuilder.core.Header(header._1, header._2)) (collection.breakOut(Array.canBuildFrom)),
          bodyJsonStream.getOrElse(null),
          request.contentType.getOrElse(null),
          request.headers.get(HeaderNames.ACCEPT).getOrElse(null)
        )
      );
      if (matchResult != null)
    } yield (rpcEntry, matchResult)

    if (matchedEntries.hasNext) {
      val (rpcEntry, matchResult) = matchedEntries.next()


      try {
        val promise = Promise[Result]
        val resp: IJsonResponseHandler = new IJsonResponseHandler {
          override def onFailure(jsonStream: JsonStream): Unit = {

            /*
            {
              com.thoughtworks.microbuilder.core.Failure : {
                STRUCTURAL_APPLICATION_FAILURE : {
                  failure : {
                      String : app structural exception
                  },
                  status : 507
                }
              }
            }
             */
            /*{
              com.thoughtworks.microbuilder.core.Failure : {
                TEXT_APPLICATION_FAILURE : {
                  message : app exception,
                  status : 507
                }
              }
            }*/
            jsonStream match {
              case JsonStreamExtractor.Object(pairs) =>

                if (pairs.hasNext) {
                  val pair = pairs.next()

                  pair.value match {
                    case JsonStreamExtractor.Object(failurePairs) =>
                      if (failurePairs.hasNext) {
                        val failurePair = failurePairs.next()
                        val expectedFailure = rpcEntry.routeConfiguration.get_failureClassName
                        failurePair.key match {
                          case "TEXT_APPLICATION_FAILURE" =>
                            if (expectedFailure == null) {
                              promise.success(textFailureResult(failurePair.value))
                            } else {
                              promise.failure(new WrongResponseFormatException(s"Expect a $expectedFailure, actually TEXT_APPLICATION_FAILURE(${PrettyTextPrinter.toString(failurePair.value)})"))
                            }
                          case "STRUCTURAL_APPLICATION_FAILURE" =>
                            if (expectedFailure != null) {
                              promise.complete(Try(structuralFailureResult(Option(matchResult.routeEntry.get_responseContentType), rpcEntry.routeConfiguration.get_failureClassName, failurePair.value)))
                            } else {
                              promise.failure(new WrongResponseFormatException(s"Expect text, actually STRUCTURAL_APPLICATION_FAILURE(${PrettyTextPrinter.toString(failurePair.value)})"))
                            }
                          case "NATIVE_FAILURE" =>
                            promise.failure(nativeFailureException(failurePair.value))
                          case _ =>
                            promise.failure(new IllegalStateException("failure must be a Failure."))
                        }
                      } else {
                        promise.failure(new IllegalStateException("failure must contain one key/value pair."))
                      }
                    case _ =>
                      promise.failure(new IllegalStateException("failure must be a JSON object."))
                  }
                } else {
                  promise.failure(new IllegalStateException("failure must contain one key/value pair."))
                }
                if (pairs.hasNext) {
                  promise.failure(new IllegalStateException("failure must contain only one key/value pair."))
                }
            }
          }

          override def onSuccess(jsonStream: JsonStream): Unit = {
            promise.success(Ok(jsonStream)(new Writeable[JsonStream]({ jsonStream =>
              val javaStream = new ByteArrayOutputStream()
              PrettyTextPrinter.print(new Output {
                override def writeByte(b: Int) = {
                  javaStream.write(b)
                }
              }, jsonStream, 0)

              javaStream.toByteArray
            }, Option(matchResult.routeEntry.get_responseContentType))))
          }
        }
        rpcEntry.incomingServiceProxy.apply(matchResult.rpcData, resp)
        promise.future
      } catch {
        // FIXME: Should handle different exceptions.
        case e: HaxeException => {
          play.api.Logger.warn(e.getObject.toString, e)
          Future.successful(BadRequest(s"Cannot parse request for $uri"))
        }
      }
    } else {
      Future.successful(NotFound(s"URL $uri does not match."))
    }
  }

  private def nativeFailureException(nativeFailureStream: JsonStream): NativeException = {
    nativeFailureStream match {
      case JsonStreamExtractor.Object(subPairs) =>
        var messageOption: Option[String] = None
        for (subPair <- subPairs) {
          subPair.key match {
            case "message" =>
              subPair.value match {
                case JsonStreamExtractor.String(messageValue) =>
                  messageOption = Some(messageValue)
              }
          }
        }
        new NativeException(messageOption.getOrElse(null))
    }
  }

  private def textFailureResult(textApplicationFailureStream: JsonStream): Result = {
    textApplicationFailureStream match {
      case JsonStreamExtractor.Object(subPairs) =>
        var messageOption: Option[String] = None
        var statusOption: Option[Int] = None
        for (subPair <- subPairs) {
          subPair.key match {
            case "message" =>
              subPair.value match {
                case JsonStreamExtractor.String(messageValue) =>
                  messageOption = Some(messageValue)
              }
            case "status" =>
              subPair.value match {
                case JsonStreamExtractor.Int32(statusValue) =>
                  statusOption = Some(statusValue)
                case JsonStreamExtractor.Number(statusValue) =>
                  statusOption = Some(statusValue.toInt)
              }
          }
        }
        val Some(status) = statusOption
        val Some(message) = messageOption
        new Status(status)(message)
    }
  }

  private def structuralFailureResult(contentType: Option[String], failureName: String, structuralApplicationFailureStream: JsonStream): Result = {
    structuralApplicationFailureStream match {
      case JsonStreamExtractor.Object(subPairs) =>
        var failureStreamOption: Option[Array[Byte]] = None
        var statusOption: Option[Int] = None
        for (subPair <- subPairs) {
          subPair.key match {
            case "status" =>
              subPair.value match {
                case JsonStreamExtractor.Int32(statusValue) =>
                  statusOption = Some(statusValue)
                case JsonStreamExtractor.Number(statusValue) =>
                  statusOption = Some(statusValue.toInt)
              }
            case "failure" =>
              subPair.value match {
                case JsonStreamExtractor.Object(failurePairs) => {
                  if (failurePairs.hasNext) {
                    val failurePair = failurePairs.next()
                    if (failurePair.key == failureName) {
                      failureStreamOption = Some(PrettyTextPrinter.toString(failurePair.value).getBytes)
                    } else {
                      throw new IllegalArgumentException(s"Failure type name does not match. Expect $failureName, actually ${failurePair.key}")
                    }
                    if (failurePairs.hasNext) {
                      throw new IllegalArgumentException("Failure JSON must contain one key/value pair.")
                    }
                  } else {
                    throw new IllegalArgumentException("Failure JSON must contain one key/value pair.")
                  }
                }
              }
          }
        }
        val Some(status) = statusOption
        val Some(failureStream) = failureStreamOption
        new Status(status)(failureStream)(Writeable[Array[Byte]](locally[Array[Byte]](_), contentType))
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy