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

play.api.inject.Module.scala Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2009-2016 Lightbend Inc. 
 */
package play.api.inject

import java.lang.reflect.Constructor
import play.api._
import scala.annotation.varargs
import scala.reflect.ClassTag

/**
 * A Play dependency injection module.
 *
 * Dependency injection modules can be used by Play plugins to provide bindings for JSR-330 compliant
 * ApplicationLoaders.  Any plugin that wants to provide components that a Play application can use may implement
 * one of these.
 *
 * Providing custom modules can be done by appending their fully qualified class names to `play.modules.enabled` in
 * `application.conf`, for example
 *
 * {{{
 *   play.modules.enabled += "com.example.FooModule"
 *   play.modules.enabled += "com.example.BarModule"
 * }}}
 *
 * It is strongly advised that in addition to providing a module for JSR-330 DI, that plugins also provide a Scala
 * trait that constructs the modules manually.  This allows for use of the module without needing a runtime dependency
 * injection provider.
 *
 * The `bind` methods are provided only as a DSL for specifying bindings. For example:
 *
 * {{{
 *   def bindings(env: Environment, conf: Configuration) = Seq(
 *     bind[Foo].to[FooImpl],
 *     bind[Bar].to(new Bar()),
 *     bind[Foo].qualifiedWith[SomeQualifier].to[OtherFoo]
 *   )
 * }}}
 */
abstract class Module {

  /**
   * Get the bindings provided by this module.
   *
   * Implementations are strongly encouraged to do *nothing* in this method other than provide bindings.  Startup
   * should be handled in the constructors and/or providers bound in the returned bindings.  Dependencies on other
   * modules or components should be expressed through constructor arguments.
   *
   * The configuration and environment a provided for the purpose of producing dynamic bindings, for example, if what
   * gets bound depends on some configuration, this may be read to control that.
   *
   * @param environment The environment
   * @param configuration The configuration
   * @return A sequence of bindings
   */
  def bindings(environment: Environment, configuration: Configuration): Seq[Binding[_]]

  /**
   * Create a binding key for the given class.
   */
  final def bind[T](clazz: Class[T]): BindingKey[T] = play.api.inject.bind(clazz)

  /**
   * Create a binding key for the given class.
   */
  final def bind[T: ClassTag]: BindingKey[T] = play.api.inject.bind[T]

  /**
   * Create a seq.
   *
   * For Java compatibility.
   */
  @varargs
  final def seq(bindings: Binding[_]*): Seq[Binding[_]] = bindings
}

/**
 * Locates and loads modules from the Play environment.
 */
object Modules {

  private val DefaultModuleName = "Module"

  /**
   * @see compare-to-play.md
   *
   * Locate the modules from the environment.
   *
   * Loads all modules specified by the play.modules.enabled property, minus the modules specified by the
   * play.modules.disabled property. If the modules have constructors that take an `Environment` and a
   * `Configuration`, then these constructors are called first; otherwise default constructors are called.
   *
   * @param environment The environment.
   * @param configuration The configuration.
   * @return A sequence of objects. This method makes no attempt to cast or check the types of the modules being loaded,
   *         allowing ApplicationLoader implementations to reuse the same mechanism to load modules specific to them.
   */
  def locate(environment: Environment, configuration: Configuration): Seq[Any] = {

    val includes = configuration.getStringSeq("play.modules.enabled").getOrElse(Seq.empty)
    val excludes = configuration.getStringSeq("play.modules.disabled").getOrElse(Seq.empty)

    val moduleClassNames = includes.toSet -- excludes

    // Construct the default module if it exists
    // Allow users to add "Module" to the excludes to exclude even attempting to look it up
    val defaultModule = if (excludes.contains(DefaultModuleName)) None else try {
      val defaultModuleClass = environment.classLoader.loadClass(DefaultModuleName).asInstanceOf[Class[Any]]
      Some(constructModule(environment, configuration, DefaultModuleName, () => defaultModuleClass))
    } catch {
      case e: ClassNotFoundException => None
    }

    moduleClassNames.map { className =>
      constructModule(environment, configuration, className,
        () => environment.classLoader.loadClass(className).asInstanceOf[Class[Any]])
    }.toSeq ++ defaultModule
  }

  private def constructModule[T](environment: Environment, configuration: Configuration, className: String, loadModuleClass: () => Class[T]): T = {
    try {
      val moduleClass = loadModuleClass()

      def tryConstruct(args: AnyRef*): Option[T] = {
        val ctor: Option[Constructor[T]] = try {
          val argTypes = args.map(_.getClass)
          Some(moduleClass.getConstructor(argTypes: _*))
        } catch {
          case _: NoSuchMethodException => None
          case _: SecurityException => None
        }
        ctor.map(_.newInstance(args: _*))
      }

      {
        tryConstruct(environment, configuration)
//        } orElse {
//          tryConstruct(new JavaEnvironment(environment), new JavaConfiguration(configuration))
      } orElse {
        tryConstruct()
      } getOrElse {
        throw new PlayException("No valid constructors", "Module [" + className + "] cannot be instantiated.")
      }
    } catch {
      case e: PlayException => throw e
      case e: VirtualMachineError => throw e
      case e: ThreadDeath => throw e
      case e: Throwable => throw new PlayException(
        "Cannot load module",
        "Module [" + className + "] cannot be instantiated.",
        e)
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy