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

com.twitter.inject.requestscope.FinagleRequestScope.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.inject.requestscope

import com.google.inject.Key
import com.google.inject.OutOfScopeException
import com.google.inject.Provider
import com.google.inject.Scope
import com.google.inject.Scopes
import com.twitter.finagle.context.Contexts
import com.twitter.inject.requestscope.FinagleRequestScope._
import java.util.{HashMap => JHashMap}
import net.codingwell.scalaguice.typeLiteral

private object FinagleRequestScope {

  /** LocalContext Key */
  val localKey = new Contexts.local.Key[JHashMap[Key[_], AnyRef]]()
  val scopeName = "FinagleRequestScope"
}

/**
 * A Guice Custom Scope implemented with [[com.twitter.util.Local]] to mimic the behavior of the
 * '@RequestScoped' scope using [[com.twitter.util.Local]] to work within the context of
 * [[com.twitter.util.Future]]s.
 *
 * @note It is expected that users use this in combination with the typed [[FinagleRequestScopeFilter]] or the
 * type agnostic [[FinagleRequestScopeFilter.TypeAgnostic]].
 *
 * @see [[https://github.com/google/guice/wiki/Scopes#scopes Guice Scopes]]
 * @see [[https://github.com/google/guice/wiki/CustomScopes Guice Custom Scopes]]
 * @see [[https://twitter.github.io/finatra/user-guide/http/filters.html HttpServer Request Scoping]]
 * @see [[https://twitter.github.io/finatra/user-guide/thrift/filters.html#request-scope ThriftServer Request Scoping]]
 */
class FinagleRequestScope extends Scope {

  /* Public */

  /**
   * Initializes the value of the RequestScope to a new empty [[java.util.HashMap]] only for the scope of the
   * current [[com.twitter.finagle.context.LocalContext]] for the given function `fn`.
   *
   * @param fn the function to execute with the given LocalContext.
   * @return the result of executing the function `fn`.
   */
  protected[requestscope] def let[R](fn: => R): R = {
    Contexts.local.let(localKey, new JHashMap[Key[_], AnyRef]())(fn)
  }

  /**
   * Seed/Add an object into the 'request scope'.
   *
   * @param value Value to seed/add into the request scope
   * @param overwrite Whether to overwrite an existing value already in the request scope (defaults to false)
   */
  def seed[T <: AnyRef: Manifest](value: T, overwrite: Boolean = false): Unit = {
    seed(Key.get(typeLiteral[T]), value, overwrite = overwrite)
  }

  /**
   * Seed/Add an object into the 'request scope'.
   *
   * @param key Key of value to be added
   * @param value Value to seed/add into the request scope
   * @param overwrite Whether to overwrite an existing value already in the request scope (defaults to false)
   */
  def seed[T <: AnyRef](key: Key[T], value: T, overwrite: Boolean): Unit = {
    val scopedObjects = getScopedObjectMap(key)

    if (!overwrite) {
      assert(
        !scopedObjects.containsKey(key),
        "A value for the key " + key + " was already seeded in this scope. " +
          "Old value: " + scopedObjects.get(key) + " New value: " + value
      )
    }

    scopedObjects.put(key, value)
  }

  override def scope[T](key: Key[T], unscopedProvider: Provider[T]): Provider[T] = new Provider[T] {
    def get: T = {
      val scopedObjects = getScopedObjectMap(key)
      val scopedObject = scopedObjects.get(key).asInstanceOf[T]
      if (scopedObject == null && !scopedObjects.containsKey(key)) {
        unscopedObject(key, unscopedProvider, scopedObjects)
      } else {
        scopedObject
      }
    }

    override def toString: String = s"$unscopedProvider[$scopeName]"
  }

  override def toString: String = scopeName

  /* Private */

  private[this] def getScopedObjectMap(key: Key[_]): JHashMap[Key[_], AnyRef] = {
    Contexts.local.get(localKey).getOrElse {
      val lookupType = key.getTypeLiteral
      throw new OutOfScopeException(
        "Cannot access " + key + " outside of a FinagledScope.\n" +
          "Ensure that the FinagleRequestScopeFilter is in your filter chain and the FinagleRequestScopeModule is a loaded Module.\n" +
          "Ensure that the filter seeding " + lookupType + " is configured and in your filter chain.\n" +
          "Ensure that you're injecting Provider[" + lookupType + "] if injecting into a Singleton.\n" +
          "Ensure that you're calling provider.get every time (and not caching/storing the providers result in a class val)"
      )
    }
  }

  // For details on CircularDependencyProxy, see https://github.com/google/guice/issues/843#issuecomment-54749202
  private[this] def unscopedObject[T](
    key: Key[T],
    unscoped: Provider[T],
    scopedObjects: JHashMap[Key[_], Object]
  ): T = {
    val current = unscoped.get()

    // don't remember proxies; these exist only to serve circular dependencies
    if (!Scopes.isCircularProxy(current)) {
      scopedObjects.put(key, current.asInstanceOf[Object])
    }

    current
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy