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

com.twitter.finatra.ResponseBuilder.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2012 Twitter Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.twitter.finatra

import org.jboss.netty.buffer.ChannelBuffer
import org.jboss.netty.handler.codec.http._
import org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1
import org.jboss.netty.buffer.ChannelBuffers.copiedBuffer
import com.twitter.finagle.http.{Response => FinagleResponse, Cookie, Status}
import org.jboss.netty.util.CharsetUtil.UTF_8
import com.twitter.util.Future
import org.apache.commons.io.IOUtils
import java.io.File
import org.jboss.netty.handler.codec.http.DefaultCookie
import com.twitter.finatra.serialization.{DefaultJacksonJsonSerializer, JsonSerializer}
import org.jboss.netty.handler.codec.http.{Cookie => NettyCookie, HttpResponseStatus}

object ResponseBuilder {
  def apply(body: String): FinagleResponse =
    new ResponseBuilder().body(body).status(200).build

  def apply(status: Int, body: String): FinagleResponse =
    new ResponseBuilder().body(body).status(status).build

  def apply(status: Int, body: String, headers: Map[String, String]): FinagleResponse =
    new ResponseBuilder().body(body).status(status).headers(headers).build
}

class ResponseBuilder(serializer:JsonSerializer = DefaultJacksonJsonSerializer) extends CommonStatuses {
  private var status:     Option[Int]          = None
  private var headers:    Map[String, String]  = Map()
  private var strBody:    Option[String]       = None
  private var binBody:    Option[Array[Byte]]  = None
  private var json:       Option[Any]          = None
  private var view:       Option[View]         = None
  private var buffer:     Option[ChannelBuffer]= None
  private var cookies:    List[Cookie]         = List()
  private var jsonSerializer: JsonSerializer     = serializer



  def contentType: Option[String] =
    this.headers.get("Content-Type")

  def withSerializer(serializer: JsonSerializer) = {
    jsonSerializer = serializer
    this
  }

  private def setContent(resp: HttpResponse): HttpResponse = {
    json match {
      case Some(j) =>
        resp.headers.set("Content-Type", "application/json")
        val jsonBytes = jsonSerializer.serialize(j)
        resp.headers.set("Content-Length", jsonBytes.length)
        resp.setContent(copiedBuffer(jsonBytes))
      case None =>
        view match {
           case Some(v) =>
             val out = v.render
             val bytes = out.getBytes(UTF_8)
             resp.headers.set("Content-Length", bytes.length)
             if (v.contentType.isDefined && !resp.headers.contains("Content-Type")) {
               resp.headers.set("Content-Type", v.contentType.get)
             }
             resp.setContent(copiedBuffer(bytes))
           case None =>
            strBody match {
              case Some(sb) =>
                val bytes = sb.getBytes(UTF_8)
                resp.headers.set("Content-Length", bytes.length)
                resp.setContent(copiedBuffer(bytes))
              case None =>
                binBody match {
                  case Some(bb) =>
                    resp.headers.set("Content-Length", bb.length)
                    resp.setContent(copiedBuffer(bb))
                  case None =>
                    buffer match {
                      case Some(b) =>
                        resp.headers.set("Content-Length", b.capacity())
                        resp.setContent(b)
                      case None => resp.headers.set("Content-Length", 0) //no content
                    }
                }
            }
        }
      }
    resp
  }

  def cookie(k: String, v: String): ResponseBuilder = {
    this.cookies ::= new Cookie(new DefaultCookie(k, v))
    this
  }

  def cookie(c: Cookie): ResponseBuilder = {
    this.cookies ::= c
    this
  }

  def cookie(c: NettyCookie): ResponseBuilder = {
    this.cookies ::= new Cookie(c)
    this
  }

  def body(s: String): ResponseBuilder = {
    this.strBody = Some(s)
    this
  }

  def status(i: Int): ResponseBuilder = {
    this.status = Some(i)
    this
  }

  def nothing: ResponseBuilder = {
    this.header("Content-Type", "text/plain")
    this.body("")
    this
  }

  def plain(body:String): ResponseBuilder = {
    this.header("Content-Type", "text/plain")
    this.body(body)
    this
  }

  def html(body:String): ResponseBuilder = {
    this.header("Content-Type", "text/html")
    this.body(body)
    this
  }

  def body(b: Array[Byte]): ResponseBuilder = {
    this.binBody = Some(b)
    this
  }

  def header(k: String, v: String): ResponseBuilder = {
    this.headers += (k -> v)
    this
  }

  def headers(m: Map[String, String]): ResponseBuilder = {
    this.headers = this.headers ++ m
    this
  }

  def json(o: Any): ResponseBuilder = {
    this.header("Content-Type", "application/json")
    this.json = Some(o)
    this
  }

  def view(v: View): ResponseBuilder = {
    this.view = Some(v)
    this
  }

  def buffer(b: ChannelBuffer): ResponseBuilder = {
    this.buffer = Some(b)
    this
  }

  def contentType(ct: String): ResponseBuilder = {
    this.header("Content-Type", ct)
    this
  }

  def static(path: String): ResponseBuilder = {
    val fullAssetPath = new File(config.assetPath(), path).toString
    if (FileResolver.hasFile(fullAssetPath) && path != '/') {
      val stream  = FileResolver.getInputStream(fullAssetPath)
      val bytes   = IOUtils.toByteArray(stream)

      stream.read(bytes)

      val mtype = FileService.extMap.getContentType('.' + fullAssetPath.split('.').last)

      this.status(200)
      this.header("Content-Type", mtype)
      this.body(bytes)
    } else {
      throw new IllegalArgumentException("File does not exist")
    }

    this
  }

  def build: FinagleResponse  = {
    build(Request())
  }

  def build(request: Request): FinagleResponse = {
    val response = request.response

    // Only set the status code if set explicitly in the builder
    this.status map response.setStatusCode

    headers.foreach { xs =>
      response.headers.add(xs._1, xs._2)
    }

    cookies foreach response.cookies.add

    setContent(response)

    response
  }

  def toFuture:Future[ResponseBuilder] = Future.value(this)

  override def toString: String = {
    val buf = new StringBuilder

    buf.append(getClass.getSimpleName)
    buf.append('\n')
    buf.append(HTTP_1_1.toString)
    buf.append(' ')
    buf.append(this.status)
    buf.append('\n')
    buf.append(this.headers)

    buf.toString()
  }

}

trait CommonStatuses { self: ResponseBuilder =>
  def ok:                  ResponseBuilder = buildFromStatus(Status.Ok)
  def created:             ResponseBuilder = buildFromStatus(Status.Created)
  def accepted:            ResponseBuilder = buildFromStatus(Status.Accepted)
  def movedPermanently:    ResponseBuilder = buildFromStatus(Status.MovedPermanently)
  def found:               ResponseBuilder = buildFromStatus(Status.Found)
  def notModified:         ResponseBuilder = buildFromStatus(Status.NotModified)
  def temporaryRedirect:   ResponseBuilder = buildFromStatus(Status.TemporaryRedirect)
  def badRequest:          ResponseBuilder = buildFromStatus(Status.BadRequest)
  def unauthorized:        ResponseBuilder = buildFromStatus(Status.Unauthorized)
  def forbidden:           ResponseBuilder = buildFromStatus(Status.Forbidden)
  def notFound:            ResponseBuilder = buildFromStatus(Status.NotFound)
  def gone:                ResponseBuilder = buildFromStatus(Status.Gone)
  def internalServerError: ResponseBuilder = buildFromStatus(Status.InternalServerError)
  def notImplemented:      ResponseBuilder = buildFromStatus(Status.NotImplemented)
  def serviceUnavailable:  ResponseBuilder = buildFromStatus(Status.ServiceUnavailable)

  private def buildFromStatus(st: HttpResponseStatus): ResponseBuilder  = {
    status(st.getCode)
    this
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy