summaryrefslogtreecommitdiffstats
path: root/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala
diff options
context:
space:
mode:
Diffstat (limited to 'container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala')
-rw-r--r--container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala205
1 files changed, 205 insertions, 0 deletions
diff --git a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala
new file mode 100644
index 00000000000..61f3c7a049c
--- /dev/null
+++ b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala
@@ -0,0 +1,205 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.container.di.componentgraph.core
+
+import java.lang.reflect.{Modifier, ParameterizedType, Constructor, Type, InvocationTargetException}
+import java.util.logging.Logger
+import com.google.inject.Inject
+
+import com.yahoo.config.ConfigInstance
+import com.yahoo.vespa.config.ConfigKey
+import com.yahoo.component.{ComponentId, AbstractComponent}
+import com.yahoo.container.di.{ConfigKeyT, JavaAnnotation, createKey, makeClassCovariant, removeStackTrace, preserveStackTrace}
+import com.yahoo.container.di.componentgraph.Provider
+
+import Node.equalEdges
+import ComponentNode._
+import java.lang.IllegalStateException
+import scala.Some
+import scala.Array
+
+/**
+ * @author tonytv
+ * @author gjoranv
+ */
+class ComponentNode(componentId: ComponentId,
+ val configId: String,
+ clazz: Class[_ <: AnyRef],
+ val XXX_key: JavaAnnotation = null) // TODO expose key, not javaAnnotation
+ extends Node(componentId)
+{
+ require(!isAbstract(clazz), "Can't instantiate abstract class " + clazz.getName)
+
+ var arguments : Array[AnyRef] = _
+
+ val constructor: Constructor[AnyRef] = bestConstructor(clazz)
+
+ var availableConfigs: Map[ConfigKeyT, ConfigInstance] = null
+
+ override val instanceKey = createKey(clazz, XXX_key)
+
+ override val instanceType = clazz
+
+ override def usedComponents: List[Node] = {
+ require(arguments != null, "Arguments must be set first.")
+ arguments.collect{case node: Node => node}.toList
+ }
+
+ override val componentType: Class[AnyRef] = {
+ def allSuperClasses(clazz: Class[_], coll : List[Class[_]]) : List[Class[_]] = {
+ if (clazz == null) coll
+ else allSuperClasses(clazz.getSuperclass, clazz :: coll)
+ }
+
+ def allGenericInterfaces(clazz : Class[_]) = allSuperClasses(clazz, List()) flatMap (_.getGenericInterfaces)
+
+ def isProvider = classOf[Provider[_]].isAssignableFrom(clazz)
+ def providerComponentType = (allGenericInterfaces(clazz).collect {
+ case t: ParameterizedType if t.getRawType == classOf[Provider[_]] => t.getActualTypeArguments.head
+ }).head
+
+ if (isProvider) providerComponentType.asInstanceOf[Class[AnyRef]] //TODO: Test what happens if you ask for something that isn't a class, e.g. a parametrized type.
+ else clazz.asInstanceOf[Class[AnyRef]]
+ }
+
+ def setArguments(arguments: Array[AnyRef]) {
+ this.arguments = arguments
+ }
+
+ def cutStackTraceAtConstructor(throwable: Throwable): Throwable = {
+ def takeUntilComponentNode(elements: Array[StackTraceElement]) =
+ elements.takeWhile(_.getClassName != classOf[ComponentNode].getName)
+
+ def dropToInitAtEnd(elements: Array[StackTraceElement]) =
+ elements.reverse.dropWhile(_.getMethodName != "<init>").reverse
+
+ val modifyStackTrace = takeUntilComponentNode _ andThen dropToInitAtEnd
+
+ val dependencyInjectorStackTraceMarker = new StackTraceElement("============= Dependency Injection =============", "newInstance", null, -1)
+
+ if (throwable != null && !preserveStackTrace) {
+ throwable.setStackTrace(modifyStackTrace(throwable.getStackTrace) :+
+ dependencyInjectorStackTraceMarker)
+
+ cutStackTraceAtConstructor(throwable.getCause)
+ }
+ throwable
+ }
+
+ override protected def newInstance() : AnyRef = {
+ assert (arguments != null, "graph.complete must be called before retrieving instances.")
+
+ val actualArguments = arguments.map {
+ case node: Node => node.newOrCachedInstance()
+ case config: ConfigKeyT => availableConfigs(config.asInstanceOf[ConfigKeyT])
+ case other => other
+ }
+
+ val instance =
+ try {
+ constructor.newInstance(actualArguments: _*)
+ } catch {
+ case e: InvocationTargetException =>
+ throw removeStackTrace(new RuntimeException(s"An exception occurred while constructing $idAndType",
+ cutStackTraceAtConstructor(e.getCause)))
+
+ }
+
+ initId(instance)
+ }
+
+ private def initId(component: AnyRef) = {
+ def checkAndSetId(c: AbstractComponent) {
+ if (c.hasInitializedId && c.getId != componentId )
+ throw new IllegalStateException("Component with id '" + componentId + "' has set a bogus component id: '" + c.getId + "'")
+
+ c.initId(componentId)
+ }
+
+ component match {
+ case component: AbstractComponent => checkAndSetId(component)
+ case other => ()
+ }
+ component
+ }
+
+ override def equals(other: Any) = {
+ other match {
+ case that: ComponentNode =>
+ super.equals(that) &&
+ equalEdges(arguments.toList, that.arguments.toList) &&
+ usedConfigs == that.usedConfigs
+ }
+ }
+
+ private def usedConfigs = {
+ require(availableConfigs != null, "setAvailableConfigs must be called!")
+ ( arguments collect {case c: ConfigKeyT => c} map (availableConfigs) ).toList
+ }
+
+ def getAnnotatedConstructorParams: Array[(Type, Array[JavaAnnotation])] = {
+ constructor.getGenericParameterTypes zip constructor.getParameterAnnotations
+ }
+
+ def setAvailableConfigs(configs: Map[ConfigKeyT, ConfigInstance]) {
+ require (arguments != null, "graph.complete must be called before graph.setAvailableConfigs.")
+ availableConfigs = configs
+ }
+
+ override def configKeys = {
+ configParameterClasses.map(new ConfigKey(_, configId)).toSet
+ }
+
+
+ private def configParameterClasses: Array[Class[ConfigInstance]] = {
+ constructor.getGenericParameterTypes.collect {
+ case clazz: Class[_] if classOf[ConfigInstance].isAssignableFrom(clazz) => clazz.asInstanceOf[Class[ConfigInstance]]
+ }
+ }
+
+ override def label = {
+ val configNames = configKeys.map(_.getName + ".def").toList
+
+ (List(instanceType.getSimpleName, Node.packageName(instanceType)) ::: configNames).
+ mkString("{", "|", "}")
+ }
+
+}
+
+object ComponentNode {
+ val log = Logger.getLogger(classOf[ComponentNode].getName)
+
+ private def bestConstructor(clazz: Class[AnyRef]) = {
+ val publicConstructors = clazz.getConstructors.asInstanceOf[Array[Constructor[AnyRef]]]
+
+ def constructorAnnotatedWithInject = {
+ publicConstructors filter {_.getAnnotation(classOf[Inject]) != null} match {
+ case Array() => None
+ case Array(single) => Some(single)
+ case _ => throwRuntimeExceptionRemoveStackTrace("Multiple constructors annotated with inject in class " + clazz.getName)
+ }
+ }
+
+ def constructorWithMostConfigParameters = {
+ def isConfigInstance(clazz: Class[_]) = classOf[ConfigInstance].isAssignableFrom(clazz)
+
+ publicConstructors match {
+ case Array() => throwRuntimeExceptionRemoveStackTrace("No public constructors in class " + clazz.getName)
+ case Array(single) => single
+ case _ =>
+ log.warning("Multiple public constructors found in class %s, there should only be one. ".format(clazz.getName) +
+ "If more than one public constructor is needed, the primary one must be annotated with @Inject.")
+ publicConstructors.
+ sortBy(_.getParameterTypes.filter(isConfigInstance).size).
+ last
+ }
+ }
+
+ constructorAnnotatedWithInject getOrElse constructorWithMostConfigParameters
+ }
+
+ private def throwRuntimeExceptionRemoveStackTrace(message: String) =
+ throw removeStackTrace(new RuntimeException(message))
+
+
+ def isAbstract(clazz: Class[_ <: AnyRef]) = Modifier.isAbstract(clazz.getModifiers)
+}