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

com.twitter.finatra.http.routing.HttpRouter.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finatra.http.routing

import com.twitter.finagle.Filter
import com.twitter.finatra.http.exceptions.ExceptionMapper
import com.twitter.finatra.http.internal.exceptions.ExceptionManager
import com.twitter.finatra.http.internal.marshalling.{CallbackConverter, MessageBodyManager}
import com.twitter.finatra.http.internal.routing.{Route, RoutesByType, RoutingService, Services}
import com.twitter.finatra.http.marshalling.MessageBodyComponent
import com.twitter.finatra.http.{Controller, HttpFilter, JavaController}
import com.twitter.inject.{Injector, Logging}
import javax.inject.{Inject, Singleton}
import scala.collection.mutable.ArrayBuffer

object HttpRouter {
  val FinatraAdminPrefix = "/admin/finatra/"
}

@Singleton
class HttpRouter @Inject()(
  injector: Injector,
  callbackConverter: CallbackConverter,
  messageBodyManager: MessageBodyManager,
  exceptionManager: ExceptionManager)
  extends Logging {

  /* Mutable State */
  private[finatra] var globalBeforeRouteMatchingFilter: HttpFilter = Filter.identity
  private[finatra] var globalFilter: HttpFilter = Filter.identity
  private[finatra] val routes = ArrayBuffer[Route]()

  private[finatra] lazy val routesByType = partitionRoutesByType()
  private[finatra] lazy val services: Services = {
    Services(
      routesByType,
      adminService = globalBeforeRouteMatchingFilter andThen new RoutingService(routesByType.admin),
      externalService = globalBeforeRouteMatchingFilter andThen new RoutingService(routesByType.external))
  }

  /* Public */

  def exceptionMapper[T <: ExceptionMapper[_]: Manifest]: HttpRouter = {
    exceptionManager.add[T]
    this
  }

  def exceptionMapper[T <: Throwable : Manifest](mapper: ExceptionMapper[T]): HttpRouter = {
    exceptionManager.add[T](mapper)
    this
  }

  def register[MBR <: MessageBodyComponent : Manifest]: HttpRouter = {
    messageBodyManager.add[MBR]()
    this
  }

  def register[MBR <: MessageBodyComponent : Manifest, ObjTypeToReadWrite: Manifest]: HttpRouter = {
    messageBodyManager.addExplicit[MBR, ObjTypeToReadWrite]()
    this
  }

  /** Add global filter used for all requests, by default applied AFTER route matching */
  def filter(clazz: Class[_ <: HttpFilter]): HttpRouter = {
    filter(injector.instance(clazz))
  }

  /** Add global filter used for all requests, optionally BEFORE route matching */
  def filter(clazz: Class[_ <: HttpFilter], beforeRouting: Boolean): HttpRouter = {
    if (beforeRouting) {
      filter(injector.instance(clazz), beforeRouting = true)
    } else {
      filter(injector.instance(clazz))
    }
  }

  /** Add global filter used for all requests, by default applied AFTER route matching */
  def filter[FilterType <: HttpFilter : Manifest]: HttpRouter = {
    filter(injector.instance[FilterType])
  }

  /** Add global filter used for all requests, optionally BEFORE route matching */
  def filter[FilterType <: HttpFilter : Manifest](beforeRouting: Boolean): HttpRouter = {
    if (beforeRouting) {
      filter(injector.instance[FilterType], beforeRouting = true)
      this
    } else {
      filter(injector.instance[FilterType])
    }
  }

  /** Add global filter used for all requests, by default applied AFTER route matching */
  def filter(filter: HttpFilter): HttpRouter = {
    assert(routes.isEmpty, "'filter' must be called before 'add'.")
    globalFilter = globalFilter andThen filter
    this
  }

  /** Add global filter used for all requests, optionally BEFORE route matching */
  def filter(filter: HttpFilter, beforeRouting: Boolean): HttpRouter = {
    if (beforeRouting) {
      assert(routes.isEmpty && globalFilter == Filter.identity,
        "'filter[T](beforeRouting = true)' must be called before 'filter' or 'add'.")
      globalBeforeRouteMatchingFilter = globalBeforeRouteMatchingFilter andThen filter
      this
    } else {
      this.filter(filter)
    }
  }

  def add(controller: Controller): HttpRouter = {
    injector.underlying.injectMembers(controller)
    addInjected(controller)
  }

  /** Add per-controller filter (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add(filter: HttpFilter, controller: Controller): HttpRouter = {
    injector.underlying.injectMembers(controller)
    addInjected(filter, controller)
  }

  def add[C <: Controller : Manifest]: HttpRouter = {
    val controller = injector.instance[C]
    addInjected(controller)
  }

  def add(clazz: Class[_ <: JavaController]): HttpRouter = {
    val controller = injector.instance(clazz)
    controller.configureRoutes()
    addInjected(controller)
  }

  // Generated
  /* Note: If you have more than 10 filters, combine some of them using MergedFilter (@see CommonFilters) */
  /** Add per-controller filter (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, F5 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4] andThen injector.instance[F5])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, F5 <: HttpFilter : Manifest, F6 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4] andThen injector.instance[F5] andThen injector.instance[F6])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, F5 <: HttpFilter : Manifest, F6 <: HttpFilter : Manifest, F7 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4] andThen injector.instance[F5] andThen injector.instance[F6] andThen injector.instance[F7])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, F5 <: HttpFilter : Manifest, F6 <: HttpFilter : Manifest, F7 <: HttpFilter : Manifest, F8 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4] andThen injector.instance[F5] andThen injector.instance[F6] andThen injector.instance[F7] andThen injector.instance[F8])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, F5 <: HttpFilter : Manifest, F6 <: HttpFilter : Manifest, F7 <: HttpFilter : Manifest, F8 <: HttpFilter : Manifest, F9 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4] andThen injector.instance[F5] andThen injector.instance[F6] andThen injector.instance[F7] andThen injector.instance[F8] andThen injector.instance[F9])

  /** Add per-controller filters (Note: Per-controller filters only run if the paired controller has a matching route) */
  def add[F1 <: HttpFilter : Manifest, F2 <: HttpFilter : Manifest, F3 <: HttpFilter : Manifest, F4 <: HttpFilter : Manifest, F5 <: HttpFilter : Manifest, F6 <: HttpFilter : Manifest, F7 <: HttpFilter : Manifest, F8 <: HttpFilter : Manifest, F9 <: HttpFilter : Manifest, F10 <: HttpFilter : Manifest, C <: Controller : Manifest]: HttpRouter = add[C](injector.instance[F1] andThen injector.instance[F2] andThen injector.instance[F3] andThen injector.instance[F4] andThen injector.instance[F5] andThen injector.instance[F6] andThen injector.instance[F7] andThen injector.instance[F8] andThen injector.instance[F9] andThen injector.instance[F10])

  /* Private */

  private def add[C <: Controller : Manifest](filter: HttpFilter): HttpRouter = {
    addInjected(
      filter,
      injector.instance[C])
  }

  private def addInjected(controller: Controller): HttpRouter = {
    routes ++= buildRoutes(controller) map { _.withFilter(globalFilter) }
    this
  }

  private def addInjected(filter: HttpFilter, controller: Controller): HttpRouter = {
    val routesWithFilter = buildRoutes(controller) map { _.withFilter(globalFilter andThen filter) }
    routes ++= routesWithFilter
    this
  }

  private def buildRoutes(controller: Controller): Seq[Route] = {
    controller.routeBuilders.map { _.build(callbackConverter, injector) }
  }

  private[finatra] def partitionRoutesByType(): RoutesByType = {
    info("Adding routes\n" + (routes.map {_.summary} mkString "\n"))
    val (adminRoutes, externalRoutes) = routes partition { route =>
      route.path.startsWith("/admin") || route.admin
    }
    assertAdminRoutes(adminRoutes)
    RoutesByType(
      external = externalRoutes.toSeq,
      admin = adminRoutes.toSeq)
  }

  // constant routes CAN start with /admin/, all others MUST start with /admin/finatra
  private def assertAdminRoutes(routes: ArrayBuffer[Route]): Unit = {
    val message = "Error adding route: %s. %s"

    for (route <- routes) {
      if (route.constantRoute) {
        // constant routes MUST start with at least /admin/
        if (!(route.path startsWith "/admin/")) {
          val msg = message.format(route.path, "Constant admin interface routes must start with prefix: /admin/")
          error(msg)
          throw new java.lang.AssertionError(msg)
        }
      } else {
        // non-constant routes MUST start with /admin/finatra/
        if(!(route.path startsWith HttpRouter.FinatraAdminPrefix)) {
          val msg = message.format(
            route.path,
            "Non-constant admin interface routes must start with prefix: " + HttpRouter.FinatraAdminPrefix)
          error(msg)
          throw new java.lang.AssertionError(msg)
        }
      }
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy