-
-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Module.scala
188 lines (169 loc) · 6.77 KB
/
Module.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
* Copyright (C) from 2022 The Play Framework Contributors <https://github.com/playframework>, 2011-2021 Lightbend Inc. <https://www.lightbend.com>
*/
package play.api.inject
import java.lang.reflect.Constructor
import scala.annotation.varargs
import scala.reflect.ClassTag
import play.{ Environment => JavaEnvironment }
import play.api._
import play.libs.reflect.ConstructorUtils
/**
* 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): scala.collection.Seq[Binding[_]]
/**
* Create a binding key for the given class.
*/
@deprecated(
"Use play.inject.Module.bindClass instead if the Module is coded in Java. Scala modules can use play.api.inject.bind[T: ClassTag]",
"2.7.0"
)
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.
*/
@deprecated("Use play.inject.Module instead if the Module is coded in Java.", "2.7.0")
@varargs
final def seq(bindings: Binding[_]*): scala.collection.Seq[Binding[_]] = bindings
}
/**
* A simple Play module, which can be configured by passing a function or a list of bindings.
*/
class SimpleModule(bindingsFunc: (Environment, Configuration) => Seq[Binding[_]]) extends Module {
def this(bindings: Binding[_]*) = this((_, _) => bindings)
final override def bindings(environment: Environment, configuration: Configuration) =
bindingsFunc(environment, configuration)
}
/**
* Locates and loads modules from the Play environment.
*/
object Modules {
private val DefaultModuleName = "Module"
/**
* 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.getOptional[Seq[String]]("play.modules.enabled").getOrElse(Seq.empty)
val excludes = configuration.getOptional[Seq[String]]("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 constructor: Option[Constructor[T]] =
try {
val argTypes = args.map(_.getClass)
Option(ConstructorUtils.getMatchingAccessibleConstructor(moduleClass, argTypes: _*))
} catch {
case _: NoSuchMethodException => None
case _: SecurityException => None
}
constructor.map(_.newInstance(args: _*))
}
{
tryConstruct(environment, configuration)
}.orElse {
tryConstruct(new JavaEnvironment(environment), configuration.underlying)
}.orElse {
tryConstruct()
}.getOrElse {
throw new PlayException(
"No valid constructors",
"Module [" + className + "] cannot be instantiated. " +
"Expected one of:\n" +
"()\n" +
"(play.api.Environment, play.api.Configuration)\n" +
"(play.Environment, com.typesafe.config.Config)"
)
}
} 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)
}
}
}