diff options
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.scala | 205 |
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) +} |