diff options
author | Olli Virtanen <olli.virtanen@oath.com> | 2018-06-21 16:48:59 +0200 |
---|---|---|
committer | Olli Virtanen <olli.virtanen@oath.com> | 2018-06-21 16:48:59 +0200 |
commit | a9f527be723d026ae21ed9504c55553bd5beadbb (patch) | |
tree | 1323e8bcc5a8f6698618d806f35a70a047cc656e /container-di/src/main/scala/com/yahoo | |
parent | 5258489bf992e8176e136362759ac079494b6f94 (diff) |
Scala code in container-di converted to Java
Diffstat (limited to 'container-di/src/main/scala/com/yahoo')
13 files changed, 0 insertions, 1693 deletions
diff --git a/container-di/src/main/scala/com/yahoo/container/bundle/MockBundle.scala b/container-di/src/main/scala/com/yahoo/container/bundle/MockBundle.scala deleted file mode 100644 index 5a28d9abe2a..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/bundle/MockBundle.scala +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.bundle - - -import java.net.URL -import java.util - -import org.osgi.framework.wiring._ -import org.osgi.framework.{ServiceReference, Version, Bundle} -import java.io.InputStream -import java.util.{Collections, Hashtable, Dictionary} -import MockBundle._ -import org.osgi.resource.{Capability, Wire, Requirement} - -/** - * @author gjoranv - */ -class MockBundle extends Bundle with BundleWiring { - - override def getState = Bundle.ACTIVE - - override def start(options: Int) {} - override def start() {} - override def stop(options: Int) {} - override def stop() {} - override def update(input: InputStream) {} - override def update() {} - override def uninstall() {} - - override def getHeaders(locale: String) = getHeaders - - override def getSymbolicName = SymbolicName - override def getVersion: Version = BundleVersion - override def getLocation = getSymbolicName - override def getBundleId: Long = 0L - - override def getHeaders: Dictionary[String, String] = new Hashtable[String, String]() - - override def getRegisteredServices = Array[ServiceReference[_]]() - override def getServicesInUse = getRegisteredServices - - override def hasPermission(permission: Any) = true - - override def getResource(name: String) = throw new UnsupportedOperationException - override def loadClass(name: String) = throw new UnsupportedOperationException - override def getResources(name: String) = throw new UnsupportedOperationException - - override def getEntryPaths(path: String) = throw new UnsupportedOperationException - override def getEntry(path: String) = throw new UnsupportedOperationException - override def findEntries(path: String, filePattern: String, recurse: Boolean) = Collections.emptyEnumeration() - - - override def getLastModified = 1L - - override def getBundleContext = throw new UnsupportedOperationException - override def getSignerCertificates(signersType: Int) = Collections.emptyMap() - - override def adapt[A](`type`: Class[A]) = - `type` match { - case MockBundle.bundleWiringClass => this.asInstanceOf[A] - case _ => ??? - } - - override def getDataFile(filename: String) = null - override def compareTo(o: Bundle) = getBundleId compareTo o.getBundleId - - - //TODO: replace with mockito - override def findEntries(p1: String, p2: String, p3: Int): util.List[URL] = ??? - override def getRequiredResourceWires(p1: String): util.List[Wire] = ??? - override def getResourceCapabilities(p1: String): util.List[Capability] = ??? - override def isCurrent: Boolean = ??? - override def getRequiredWires(p1: String): util.List[BundleWire] = ??? - override def getCapabilities(p1: String): util.List[BundleCapability] = ??? - override def getProvidedResourceWires(p1: String): util.List[Wire] = ??? - override def getProvidedWires(p1: String): util.List[BundleWire] = ??? - override def getRevision: BundleRevision = ??? - override def getResourceRequirements(p1: String): util.List[Requirement] = ??? - override def isInUse: Boolean = ??? - override def listResources(p1: String, p2: String, p3: Int): util.Collection[String] = Collections.emptyList() - override def getClassLoader: ClassLoader = MockBundle.getClass.getClassLoader - override def getRequirements(p1: String): util.List[BundleRequirement] = ??? - override def getResource: BundleRevision = ??? - override def getBundle: Bundle = ??? -} - -object MockBundle { - val SymbolicName = "mock-bundle" - val BundleVersion = new Version(1, 0, 0) - - val bundleWiringClass = classOf[BundleWiring] - - -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/CloudSubscriberFactory.scala b/container-di/src/main/scala/com/yahoo/container/di/CloudSubscriberFactory.scala deleted file mode 100644 index 0f3fab93e80..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/CloudSubscriberFactory.scala +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di - -import java.util.logging.{Level, Logger} - -import com.yahoo.config.ConfigInstance -import com.yahoo.config.subscription.{ConfigHandle, ConfigSource, ConfigSourceSet, ConfigSubscriber} -import com.yahoo.container.di.CloudSubscriberFactory._ -import com.yahoo.container.di.config.{Subscriber, SubscriberFactory} -import com.yahoo.vespa.config.ConfigKey - -import scala.collection.JavaConverters._ -import scala.language.existentials - - -/** - * @author Tony Vaagenes - */ -class CloudSubscriberFactory(configSource: ConfigSource) extends SubscriberFactory { - - private var testGeneration: Option[Long] = None - - private val activeSubscribers = new java.util.WeakHashMap[CloudSubscriber, Int]() - - override def getSubscriber(configKeys: java.util.Set[_ <: ConfigKey[_]]): Subscriber = { - val subscriber = new CloudSubscriber(configKeys.asScala.toSet.asInstanceOf[Set[ConfigKeyT]], configSource) - - testGeneration.foreach(subscriber.subscriber.reload(_)) //TODO: test specific code, remove - activeSubscribers.put(subscriber, 0) - - subscriber - } - - //TODO: test specific code, remove - override def reloadActiveSubscribers(generation: Long) { - testGeneration = Some(generation) - - val l = activeSubscribers.keySet().asScala.toSet - l.foreach { _.subscriber.reload(generation) } - } -} - -object CloudSubscriberFactory { - val log = Logger.getLogger(classOf[CloudSubscriberFactory].getName) - - private class CloudSubscriber(keys: Set[ConfigKeyT], configSource: ConfigSource) extends Subscriber - { - private[CloudSubscriberFactory] val subscriber = new ConfigSubscriber(configSource) - private val handles: Map[ConfigKeyT, ConfigHandle[_ <: ConfigInstance]] = keys.map(subscribe).toMap - - - // if waitNextGeneration has not yet been called, -1 should be returned - var generation: Long = -1 - - // True if this reconfiguration was caused by a system-internal redeploy, not an external application change - var internalRedeploy: Boolean = false - - private def subscribe(key: ConfigKeyT) = (key, subscriber.subscribe(key.getConfigClass, key.getConfigId)) - - override def configChanged = handles.values.exists(_.isChanged) - - //mapValues returns a view,, so we need to force evaluation of it here to prevent deferred evaluation. - override def config = handles.mapValues(_.getConfig).toMap.view.force. - asInstanceOf[Map[ConfigKey[ConfigInstance], ConfigInstance]].asJava - - override def waitNextGeneration() = { - require(!handles.isEmpty) - - /* Catch and just log config exceptions due to missing config values for parameters that do - * not have a default value. These exceptions occur when the user has removed a component - * from services.xml, and the component takes a config that has parameters without a - * default value in the def-file. There is a new 'components' config underway, where the - * component is removed, so this old config generation will soon be replaced by a new one. */ - var gotNextGen = false - var numExceptions = 0 - while (!gotNextGen) { - try{ - if (subscriber.nextGeneration()) - gotNextGen = true - } catch { - case e: IllegalArgumentException => - numExceptions += 1 - log.log(Level.WARNING, "Got exception from the config system (please ignore the exception if you just removed " - + "a component from your application that used the mentioned config): ", e) - if (numExceptions >= 5) - throw new IllegalArgumentException("Failed retrieving the next config generation.", e) - } - } - - generation = subscriber.getGeneration - internalRedeploy = subscriber.isInternalRedeploy - generation - } - - override def close() { - subscriber.close() - } - } - - - class Provider extends com.google.inject.Provider[SubscriberFactory] { - override def get() = new CloudSubscriberFactory(ConfigSourceSet.createDefault()) - } -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/ConfigRetriever.scala b/container-di/src/main/scala/com/yahoo/container/di/ConfigRetriever.scala deleted file mode 100644 index aad9e17acb2..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/ConfigRetriever.scala +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di - - -import java.util.logging.{Level, Logger} - -import com.yahoo.config.ConfigInstance -import com.yahoo.container.di.ConfigRetriever._ -import com.yahoo.container.di.config.Subscriber -import com.yahoo.log.LogLevel.DEBUG - -import scala.annotation.tailrec -import scala.collection.JavaConverters._ -import scala.language.postfixOps - -/** - * @author Tony Vaagenes - * @author gjoranv - */ -final class ConfigRetriever(bootstrapKeys: Set[ConfigKeyT], - subscribe: Set[ConfigKeyT] => Subscriber) -{ - require(!bootstrapKeys.isEmpty) - - private val bootstrapSubscriber: Subscriber = subscribe(bootstrapKeys) - - private var componentSubscriber: Subscriber = subscribe(Set()) - private var componentSubscriberKeys: Set[ConfigKeyT] = Set() - - /** Loop forever until we get config */ - @tailrec - final def getConfigs(componentConfigKeys: Set[ConfigKeyT], leastGeneration: Long, restartOnRedeploy: Boolean = false): ConfigSnapshot = { - require(componentConfigKeys intersect bootstrapKeys isEmpty) - log.log(DEBUG, "getConfigs: " + componentConfigKeys) - - setupComponentSubscriber(componentConfigKeys ++ bootstrapKeys) - - getConfigsOptional(leastGeneration, restartOnRedeploy) match { - case Some(snapshot) => resetComponentSubscriberIfBootstrap(snapshot); snapshot - case None => getConfigs(componentConfigKeys, leastGeneration, restartOnRedeploy) - } - } - - - /** Try to get config just once */ - final def getConfigsOnce(componentConfigKeys: Set[ConfigKeyT], leastGeneration: Long, restartOnRedeploy: Boolean = false): Option[ConfigSnapshot] = { - require(componentConfigKeys intersect bootstrapKeys isEmpty) - log.log(DEBUG, "getConfigsOnce: " + componentConfigKeys) - - setupComponentSubscriber(componentConfigKeys ++ bootstrapKeys) - - getConfigsOptional(leastGeneration, restartOnRedeploy) match { - case Some(snapshot) => resetComponentSubscriberIfBootstrap(snapshot); Some(snapshot) - case None => None; - } - } - - private def getConfigsOptional(leastGeneration: Long, restartOnRedeploy: Boolean): Option[ConfigSnapshot] = { - val newestComponentGeneration = componentSubscriber.waitNextGeneration() - log.log(DEBUG, s"getConfigsOptional: new component generation: $newestComponentGeneration") - - // leastGeneration is only used to ensure newer generation when the previous generation was invalidated due to an exception - if (newestComponentGeneration < leastGeneration) { - None - } else if (restartOnRedeploy && ! componentSubscriber.internalRedeploy()) { // Don't reconfig - wait for restart - None - } else if (bootstrapSubscriber.generation < newestComponentGeneration) { - val newestBootstrapGeneration = bootstrapSubscriber.waitNextGeneration() - log.log(DEBUG, s"getConfigsOptional: new bootstrap generation: ${bootstrapSubscriber.generation}") - bootstrapConfigIfChanged() orElse { - if (newestBootstrapGeneration == newestComponentGeneration){ - log.log(DEBUG, s"Got new components configs with unchanged bootstrap configs.") - componentsConfigIfChanged() - } else { - // This should not be a normal case, and hence a warning to allow investigation. - log.warning(s"Did not get same generation for bootstrap ($newestBootstrapGeneration) and components configs ($newestComponentGeneration).") - None - } - } - } else { - // bootstrapGen==componentGen (happens only when a new component subscriber returns first config after bootstrap) - componentsConfigIfChanged() - } - } - - private def bootstrapConfigIfChanged(): Option[BootstrapConfigs] = configIfChanged(bootstrapSubscriber, BootstrapConfigs) - private def componentsConfigIfChanged(): Option[ComponentsConfigs] = configIfChanged(componentSubscriber, ComponentsConfigs) - - private def configIfChanged[T <: ConfigSnapshot](subscriber: Subscriber, - constructor: Map[ConfigKeyT, ConfigInstance] => T ): Option[T] = { - if (subscriber.configChanged) Some(constructor(subscriber.config.asScala.toMap)) - else None - } - - private def resetComponentSubscriberIfBootstrap(snapshot: ConfigSnapshot) { - snapshot match { - case BootstrapConfigs(_) => setupComponentSubscriber(Set()) - case _ => - } - } - - private def setupComponentSubscriber(keys: Set[ConfigKeyT]) { - if (componentSubscriberKeys != keys) { - componentSubscriber.close() - componentSubscriberKeys = keys - try { - log.log(DEBUG, s"Setting up new component subscriber for keys: $keys") - componentSubscriber = subscribe(keys) - } catch { - case e: Throwable => - log.log(Level.WARNING, s"Failed setting up subscriptions for component configs: ${e.getMessage}") - log.log(Level.WARNING, s"Config keys: $keys") - throw e - } - } - } - - def shutdown() { - bootstrapSubscriber.close() - componentSubscriber.close() - } - - //TODO: check if these are really needed - final def getBootstrapGeneration = bootstrapSubscriber.generation - final def getComponentsGeneration = componentSubscriber.generation -} - - -object ConfigRetriever { - private val log = Logger.getLogger(classOf[ConfigRetriever].getName) - - sealed abstract class ConfigSnapshot - case class BootstrapConfigs(configs: Map[ConfigKeyT, ConfigInstance]) extends ConfigSnapshot - case class ComponentsConfigs(configs: Map[ConfigKeyT, ConfigInstance]) extends ConfigSnapshot -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/Container.scala b/container-di/src/main/scala/com/yahoo/container/di/Container.scala deleted file mode 100644 index 2a185d41a6c..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/Container.scala +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di - -import java.util.logging.{Level, Logger} -import java.util.{IdentityHashMap, Random} - -import com.google.inject.{Guice, Injector} -import com.yahoo.config._ -import com.yahoo.config.subscription.ConfigInterruptedException -import com.yahoo.container.bundle.BundleInstantiationSpecification -import com.yahoo.container.di.ConfigRetriever.{BootstrapConfigs, ComponentsConfigs} -import com.yahoo.container.di.Container._ -import com.yahoo.container.di.componentgraph.core.ComponentNode.ComponentConstructorException -import com.yahoo.container.di.componentgraph.core.{ComponentGraph, ComponentNode, JerseyNode} -import com.yahoo.container.di.config.{RestApiContext, SubscriberFactory} -import com.yahoo.container.{BundlesConfig, ComponentsConfig} -import com.yahoo.log.LogLevel.DEBUG -import com.yahoo.protect.Process -import com.yahoo.vespa.config.ConfigKey - -import scala.collection.JavaConverters._ -import scala.concurrent.duration._ -import scala.language.postfixOps -import scala.math.max - - -/** - * - * @author gjoranv - * @author Tony Vaagenes - */ -class Container( - subscriberFactory: SubscriberFactory, - configId: String, - componentDeconstructor: ComponentDeconstructor, - osgi: Osgi = new Osgi {} - ) -{ - val bundlesConfigKey = new ConfigKey(classOf[BundlesConfig], configId) - val componentsConfigKey = new ConfigKey(classOf[ComponentsConfig], configId) - - var configurer = new ConfigRetriever(Set(bundlesConfigKey, componentsConfigKey), (keys) => subscriberFactory.getSubscriber(keys.asJava)) - var previousConfigGeneration = -1L - var leastGeneration = -1L - - @throws(classOf[InterruptedException]) - def getNewComponentGraph(oldGraph: ComponentGraph = new ComponentGraph, - fallbackInjector: GuiceInjector = Guice.createInjector(), - restartOnRedeploy: Boolean = false): ComponentGraph = { - - def deconstructObsoleteComponents(oldGraph: ComponentGraph, newGraph: ComponentGraph) { - val oldComponents = new IdentityHashMap[AnyRef, AnyRef]() - oldGraph.allComponentsAndProviders foreach (oldComponents.put(_, null)) - newGraph.allComponentsAndProviders foreach (oldComponents.remove(_)) - oldComponents.keySet.asScala foreach (componentDeconstructor.deconstruct(_)) - } - - try { - val newGraph = getConfigAndCreateGraph(oldGraph, fallbackInjector, restartOnRedeploy) - newGraph.reuseNodes(oldGraph) - constructComponents(newGraph) - deconstructObsoleteComponents(oldGraph, newGraph) - newGraph - } catch { - case userException: ComponentConstructorException => - invalidateGeneration(oldGraph.generation, userException) - // TODO: Wrap userException in an Error when generation==0 (+ unit test that Error is thrown) - throw userException - case t: Throwable => - invalidateGeneration(oldGraph.generation, t) - throw t - } - } - - private def invalidateGeneration(generation: Long, cause: Throwable) { - val maxWaitToExit = 60 seconds - - def newGraphErrorMessage(generation: Long, cause: Throwable): String = { - val failedFirstMessage = "Failed to set up first component graph" - val failedNewMessage = "Failed to set up new component graph" - val constructMessage = "due to error when constructing one of the components" - val exitMessage = s"Exiting within $maxWaitToExit." - val retainMessage = "Retaining previous component generation." - generation match { - case 0 => - cause match { - case _: ComponentConstructorException => s"$failedFirstMessage $constructMessage. $exitMessage" - case _ => s"$failedFirstMessage. $exitMessage" - } - case _ => - cause match { - case _: ComponentConstructorException => s"$failedNewMessage $constructMessage. $retainMessage" - case _ => s"$failedNewMessage. $retainMessage" - } - } - } - - // TODO: move to ConfiguredApplication - def logAndDie(message: String, cause: Throwable): Unit = { - log.log(Level.SEVERE, message, cause) - try { - Thread.sleep((new Random(System.nanoTime).nextDouble * maxWaitToExit.toMillis).toLong) - } catch { - case _: InterruptedException => // Do nothing - } - Process.logAndDie("Exited for reason (repeated from above):", cause) - } - - leastGeneration = max(configurer.getComponentsGeneration, configurer.getBootstrapGeneration) + 1 - cause match { - case _: InterruptedException | _: ConfigInterruptedException => // Normal during shutdown, do not log anything. - case _ => log.log(Level.WARNING, newGraphErrorMessage(generation, cause), cause) - } - } - - final def getConfigAndCreateGraph(graph: ComponentGraph = new ComponentGraph, - fallbackInjector: Injector, - restartOnRedeploy: Boolean): ComponentGraph = { - - val snapshot = configurer.getConfigs(graph.configKeys, leastGeneration, restartOnRedeploy) - - log.log(DEBUG, - """createNewGraph: - |graph.configKeys = %s - |graph.generation = %s - |snapshot = %s""" - .format(graph.configKeys, graph.generation, snapshot).stripMargin) - - val preventTailRecursion = - snapshot match { - case BootstrapConfigs(configs) => - // TODO: remove require when proven unnecessary - require(getBootstrapGeneration > previousConfigGeneration, - """Got bootstrap configs out of sequence for old config generation %d. - |Previous config generation is %d""".format(getBootstrapGeneration, previousConfigGeneration)) - log.log(DEBUG, - """Got new bootstrap generation - |bootstrap generation = %d - |components generation: %d - |previous generation: %d""" - .format(getBootstrapGeneration, getComponentsGeneration, previousConfigGeneration).stripMargin) - installBundles(configs) - getConfigAndCreateGraph( - createComponentsGraph(configs, getBootstrapGeneration,fallbackInjector), fallbackInjector, restartOnRedeploy) - case ComponentsConfigs(configs) => - log.log(DEBUG, - """Got components configs, - |bootstrap generation = %d - |components generation: %d - |previous generation: %d""" - .format(getBootstrapGeneration, getComponentsGeneration, previousConfigGeneration).stripMargin) - createAndConfigureComponentsGraph(configs, fallbackInjector) - } - - preventTailRecursion - } - - - def getBootstrapGeneration: Long = { - configurer.getBootstrapGeneration - } - - def getComponentsGeneration: Long = { - configurer.getComponentsGeneration - } - - private def createAndConfigureComponentsGraph[T](componentsConfigs: Map[ConfigKeyT, ConfigInstance], - fallbackInjector: Injector): ComponentGraph = { - - val componentGraph = createComponentsGraph(componentsConfigs, getComponentsGeneration, fallbackInjector) - componentGraph.setAvailableConfigs(componentsConfigs) - componentGraph - } - - def injectNodes(config: ComponentsConfig, graph: ComponentGraph) { - for { - component <- config.components().asScala - inject <- component.inject().asScala - } { - def getNode = ComponentGraph.getNode(graph, _: String) - - //TODO: Support inject.name() - getNode(component.id()).inject(getNode(inject.id())) - } - - } - - def installBundles(configsIncludingBootstrapConfigs: Map[ConfigKeyT, ConfigInstance]) { - val bundlesConfig = getConfig(bundlesConfigKey, configsIncludingBootstrapConfigs) - osgi.useBundles(bundlesConfig.bundle()) - } - - private def createComponentsGraph[T]( - configsIncludingBootstrapConfigs: Map[ConfigKeyT, ConfigInstance], - generation: Long, - fallbackInjector: Injector): ComponentGraph = { - - previousConfigGeneration = generation - - val graph = new ComponentGraph(generation) - - val componentsConfig = getConfig(componentsConfigKey, configsIncludingBootstrapConfigs) - if (componentsConfig == null) - throw new ConfigurationRuntimeException( - "The set of all configs does not include a valid 'components' config. Config set: " + configsIncludingBootstrapConfigs.keySet) - addNodes(componentsConfig, graph) - injectNodes(componentsConfig, graph) - - graph.complete(fallbackInjector) - graph - } - - def addNodes[T](componentsConfig: ComponentsConfig, graph: ComponentGraph) { - def isRestApiContext(clazz: Class[_]) = classOf[RestApiContext].isAssignableFrom(clazz) - def asRestApiContext(clazz: Class[_]) = clazz.asInstanceOf[Class[RestApiContext]] - - for (config : ComponentsConfig.Components <- componentsConfig.components.asScala) { - val specification = bundleInstatiationSpecification(config) - val componentClass = osgi.resolveClass(specification) - - val componentNode = - if (isRestApiContext(componentClass)) - new JerseyNode(specification.id, config.configId(), asRestApiContext(componentClass), osgi) - else - new ComponentNode(specification.id, config.configId(), componentClass) - - graph.add(componentNode) - } - } - - private def constructComponents(graph: ComponentGraph) { - graph.nodes foreach (_.newOrCachedInstance()) - } - - def shutdown(graph: ComponentGraph, deconstructor: ComponentDeconstructor) { - shutdownConfigurer() - if (graph != null) - deconstructAllComponents(graph, deconstructor) - } - - def shutdownConfigurer() { - configurer.shutdown() - } - - // Reload config manually, when subscribing to non-configserver sources - def reloadConfig(generation: Long) { - subscriberFactory.reloadActiveSubscribers(generation) - } - - def deconstructAllComponents(graph: ComponentGraph, deconstructor: ComponentDeconstructor) { - graph.allComponentsAndProviders foreach(deconstructor.deconstruct(_)) - } - -} - -object Container { - val log = Logger.getLogger(classOf[Container].getName) - - def getConfig[T <: ConfigInstance](key: ConfigKey[T], configs: Map[ConfigKeyT, ConfigInstance]) : T = { - key.getConfigClass.cast(configs.getOrElse(key.asInstanceOf[ConfigKeyT], sys.error("Missing config " + key))) - } - - def bundleInstatiationSpecification(config: ComponentsConfig.Components) = - BundleInstantiationSpecification.getFromStrings(config.id(), config.classId(), config.bundle()) -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/Osgi.scala b/container-di/src/main/scala/com/yahoo/container/di/Osgi.scala deleted file mode 100644 index 3407eceae3e..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/Osgi.scala +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di - -import com.yahoo.config.FileReference -import com.yahoo.container.bundle.{MockBundle, BundleInstantiationSpecification} -import com.yahoo.container.di.Osgi.BundleClasses -import org.osgi.framework.Bundle -import com.yahoo.component.ComponentSpecification - -/** - * - * @author gjoranv - * @author Tony Vaagenes - */ -trait Osgi { - - def getBundleClasses(bundle: ComponentSpecification, packagesToScan: Set[String]): BundleClasses = { - BundleClasses(new MockBundle, List()) - } - - def useBundles(bundles: java.util.Collection[FileReference]) { - println("useBundles " + bundles.toArray.mkString(", ")) - } - - def resolveClass(spec: BundleInstantiationSpecification): Class[AnyRef] = { - println("resolving class " + spec.classId) - Class.forName(spec.classId.getName).asInstanceOf[Class[AnyRef]] - } - - def getBundle(spec: ComponentSpecification): Bundle = { - println("resolving bundle " + spec) - new MockBundle() - } - -} - -object Osgi { - type RelativePath = String //e.g. "com/yahoo/MyClass.class" - case class BundleClasses(bundle: Bundle, classEntries: Iterable[RelativePath]) -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentGraph.scala b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentGraph.scala deleted file mode 100644 index 92f489798c9..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentGraph.scala +++ /dev/null @@ -1,334 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.util.logging.Logger - -import com.yahoo.component.provider.ComponentRegistry -import com.yahoo.config.ConfigInstance - -import java.lang.annotation.{Annotation => JavaAnnotation} -import java.lang.IllegalStateException -import com.yahoo.log.LogLevel - -import collection.mutable -import annotation.tailrec - -import com.yahoo.container.di.{ConfigKeyT, GuiceInjector} -import com.yahoo.container.di.componentgraph.Provider -import com.yahoo.vespa.config.ConfigKey -import com.google.inject.{Guice, ConfigurationException, Key, BindingAnnotation} -import net.jcip.annotations.NotThreadSafe - -import com.yahoo.component.{AbstractComponent, ComponentId} -import java.lang.reflect.{TypeVariable, WildcardType, Method, ParameterizedType, Type} -import com.yahoo.container.di.removeStackTrace -import scala.util.Try -import scala.Some - -import scala.language.existentials - -/** - * @author Tony Vaagenes - * @author gjoranv - */ -@NotThreadSafe -class ComponentGraph(val generation: Long = 0) { - - import ComponentGraph._ - - private var nodesById = Map[ComponentId, Node]() - - private[di] def size = nodesById.size - - def nodes = nodesById.values - - def add(component: Node) { - require(!nodesById.isDefinedAt(component.componentId), "Multiple components with the same id " + component.componentId) - nodesById += component.componentId -> component - } - - def lookupGlobalComponent(key: Key[_]): Option[Node] = { - require(key.getTypeLiteral.getType.isInstanceOf[Class[_]], "Type not supported " + key.getTypeLiteral) - val clazz = key.getTypeLiteral.getRawType - - - val components = matchingComponentNodes(nodesById.values, key) - - def singleNonProviderComponentOrThrow: Option[Node] = { - val nonProviderComponents = components filter (c => !classOf[Provider[_]].isAssignableFrom(c.instanceType)) - nonProviderComponents.size match { - case 0 => throw new IllegalStateException(s"Multiple global component providers for class '${clazz.getName}' found") - case 1 => Some(nonProviderComponents.head.asInstanceOf[Node]) - case _ => throw new IllegalStateException(s"Multiple global components with class '${clazz.getName}' found") - } - } - - components.size match { - case 0 => None - case 1 => Some(components.head.asInstanceOf[Node]) - case _ => singleNonProviderComponentOrThrow - } - } - - def getInstance[T](clazz: Class[T]) : T = { - getInstance(Key.get(clazz)) - } - - def getInstance[T](key: Key[T]) : T = { - lookupGlobalComponent(key). - // TODO: Combine exception handling with lookupGlobalComponent. - getOrElse(throw new IllegalStateException("No global component with key '%s' ".format(key.toString))). - newOrCachedInstance().asInstanceOf[T] - } - - private def componentNodes: Traversable[ComponentNode] = - nodesOfType(nodesById.values, classOf[ComponentNode]) - - private def componentRegistryNodes: Traversable[ComponentRegistryNode] = - nodesOfType(nodesById.values, classOf[ComponentRegistryNode]) - - private def osgiComponentsOfClass(clazz: Class[_]): Traversable[ComponentNode] = { - componentNodes.filter(node => clazz.isAssignableFrom(node.componentType)) - } - - def complete(fallbackInjector: GuiceInjector = Guice.createInjector()) { - def detectCycles = topologicalSort(nodesById.values.toList) - - componentNodes foreach {completeNode(_, fallbackInjector)} - componentRegistryNodes foreach completeComponentRegistryNode - detectCycles - } - - def configKeys: Set[ConfigKeyT] = { - nodesById.values.flatMap(_.configKeys).toSet - } - - def setAvailableConfigs(configs: Map[ConfigKeyT, ConfigInstance]) { - componentNodes foreach { _.setAvailableConfigs(configs) } - } - - def reuseNodes(old: ComponentGraph) { - def copyInstancesIfNodeEqual() { - val commonComponentIds = nodesById.keySet & old.nodesById.keySet - for (id <- commonComponentIds) { - if (nodesById(id) == old.nodesById(id)) { - nodesById(id).instance = old.nodesById(id).instance - } - } - } - def resetInstancesWithModifiedDependencies() { - for { - node <- topologicalSort(nodesById.values.toList) - usedComponent <- node.usedComponents - } { - if (usedComponent.instance == None) { - node.instance = None - } - } - } - - copyInstancesIfNodeEqual() - resetInstancesWithModifiedDependencies() - } - - def allComponentsAndProviders = nodes map {_.instance.get} - - private def completeComponentRegistryNode(registry: ComponentRegistryNode) { - registry.injectAll(osgiComponentsOfClass(registry.componentClass)) - } - - private def completeNode(node: ComponentNode, fallbackInjector: GuiceInjector) { - try { - val arguments = node.getAnnotatedConstructorParams.map(handleParameter(node, fallbackInjector, _)) - - node.setArguments(arguments) - } catch { - case e : Exception => throw removeStackTrace(new RuntimeException(s"When resolving dependencies of ${node.idAndType}", e)) - } - } - - private def handleParameter(node : Node, - fallbackInjector: GuiceInjector, - annotatedParameterType: (Type, Array[JavaAnnotation])): AnyRef = - { - def isConfigParameter(clazz : Class[_]) = classOf[ConfigInstance].isAssignableFrom(clazz) - def isComponentRegistry(t : Type) = t == classOf[ComponentRegistry[_]] - - val (parameterType, annotations) = annotatedParameterType - - (parameterType match { - case componentIdClass: Class[_] if componentIdClass == classOf[ComponentId] => node.componentId - case configClass : Class[_] if isConfigParameter(configClass) => handleConfigParameter(node.asInstanceOf[ComponentNode], configClass) - case registry : ParameterizedType if isComponentRegistry(registry.getRawType) => getComponentRegistry(registry.getActualTypeArguments.head) - case clazz : Class[_] => handleComponentParameter(node, fallbackInjector, clazz, annotations) - case other: ParameterizedType => sys.error(s"Injection of parameterized type $other is not supported.") - case other => sys.error(s"Injection of type $other is not supported.") - }).asInstanceOf[AnyRef] - } - - - def newComponentRegistryNode(componentClass: Class[AnyRef]): ComponentRegistryNode = { - val registry = new ComponentRegistryNode(componentClass) - add(registry) //TODO: don't mutate nodes here. - registry - } - - private def getComponentRegistry(componentType : Type) : ComponentRegistryNode = { - val componentClass = componentType match { - case wildCardType: WildcardType => - assert(wildCardType.getLowerBounds.isEmpty) - assert(wildCardType.getUpperBounds.size == 1) - wildCardType.getUpperBounds.head.asInstanceOf[Class[AnyRef]] - case clazz: Class[_] => clazz - case typeVariable: TypeVariable[_] => - throw new RuntimeException("Can't create ComponentRegistry of unknown type variable " + typeVariable) - } - - componentRegistryNodes.find(_.componentClass == componentType). - getOrElse(newComponentRegistryNode(componentClass.asInstanceOf[Class[AnyRef]])) - } - - def handleConfigParameter(node : ComponentNode, clazz: Class[_]) : ConfigKeyT = { - new ConfigKey(clazz.asInstanceOf[Class[ConfigInstance]], node.configId) - } - - def getKey(clazz: Class[_], bindingAnnotation: Option[JavaAnnotation]) = - bindingAnnotation.map(Key.get(clazz, _)).getOrElse(Key.get(clazz)) - - private def handleComponentParameter(node: Node, - fallbackInjector: GuiceInjector, - clazz: Class[_], - annotations: Array[JavaAnnotation]) : Node = { - - val bindingAnnotations = annotations.filter(isBindingAnnotation) - val key = getKey(clazz, bindingAnnotations.headOption) - - def matchingGuiceNode(key: Key[_], instance: AnyRef): Option[GuiceNode] = { - matchingNodes(nodesById.values, classOf[GuiceNode], key). - filter(node => node.newOrCachedInstance eq instance). // TODO: assert that there is only one (after filter) - headOption - } - - def lookupOrCreateGlobalComponent: Node = { - lookupGlobalComponent(key).getOrElse { - val instance = - try { - log.log(LogLevel.DEBUG, "Trying the fallback injector to create" + messageForNoGlobalComponent(clazz, node)) - fallbackInjector.getInstance(key).asInstanceOf[AnyRef] - } catch { - case e: ConfigurationException => - throw removeStackTrace(new IllegalStateException( - if (messageForMultipleClassLoaders(clazz).isEmpty) - "No global" + messageForNoGlobalComponent(clazz, node) - else - messageForMultipleClassLoaders(clazz))) - - } - matchingGuiceNode(key, instance).getOrElse { - val node = new GuiceNode(instance, key.getAnnotation) - add(node) - node - } - } - } - - if (bindingAnnotations.size > 1) - sys.error("More than one binding annotation used in class '%s'".format(node.instanceType)) - - val injectedNodesOfCorrectType = matchingComponentNodes(node.componentsToInject, key) - injectedNodesOfCorrectType.size match { - case 0 => lookupOrCreateGlobalComponent - case 1 => injectedNodesOfCorrectType.head.asInstanceOf[Node] - case _ => sys.error("Multiple components of type '%s' injected into component '%s'".format(clazz.getName, node.instanceType)) //TODO: !className for last parameter - } - } - -} - -object ComponentGraph { - val log = Logger.getLogger(classOf[ComponentGraph].getName) - - def messageForNoGlobalComponent(clazz: Class[_], node: Node) = - s" component of class ${clazz.getName} to inject into component ${node.idAndType}." - - def messageForMultipleClassLoaders(clazz: Class[_]): String = { - val errMsg = "Class " + clazz.getName + " is provided by the framework, and cannot be embedded in a user bundle. " + - "To resolve this problem, please refer to osgi-classloading.html#multiple-implementations in the documentation" - - (for { - resolvedClass <- Try {Class.forName(clazz.getName, false, this.getClass.getClassLoader)} - if resolvedClass != clazz - } yield errMsg) - .getOrElse("") - } - - // For unit testing - def getNode(graph: ComponentGraph, componentId: String): Node = { - graph.nodesById(new ComponentId(componentId)) - } - - private def nodesOfType[T <: Node](nodes: Traversable[Node], clazz : Class[T]) : Traversable[T] = { - nodes.collect { - case node if clazz.isInstance(node) => clazz.cast(node) - } - } - - private def matchingComponentNodes(nodes: Traversable[Node], key: Key[_]) : Traversable[ComponentNode] = { - matchingNodes(nodes, classOf[ComponentNode], key) - } - - // Finds all nodes with a given nodeType and instance with given key - private def matchingNodes[T <: Node](nodes: Traversable[Node], nodeType: Class[T], key: Key[_]) : Traversable[T] = { - val clazz = key.getTypeLiteral.getRawType - val annotation = key.getAnnotation - - val filteredByClass = nodesOfType(nodes, nodeType) filter { node => clazz.isAssignableFrom(node.componentType) } - val filteredByClassAndAnnotation = filteredByClass filter { node => annotation == node.instanceKey.getAnnotation } - - if (filteredByClass.size == 1) filteredByClass - else if (filteredByClassAndAnnotation.size > 0) filteredByClassAndAnnotation - else filteredByClass - } - - // Returns true if annotation is a BindingAnnotation, e.g. com.google.inject.name.Named - def isBindingAnnotation(annotation: JavaAnnotation) : Boolean = { - def isBindingAnnotation(clazz: Class[_]) : Boolean = { - val clazzOrSuperIsBindingAnnotation = - (clazz.getAnnotation(classOf[BindingAnnotation]) != null) || - Option(clazz.getSuperclass).map(isBindingAnnotation).getOrElse(false) - - (clazzOrSuperIsBindingAnnotation /: clazz.getInterfaces.map(isBindingAnnotation))(_ || _) - } - isBindingAnnotation(annotation.getClass) - } - - /** - * The returned list is the nodes from the graph bottom-up. - * @return A list where a earlier than b in the list implies that there is no path from a to b - */ - def topologicalSort(nodes: List[Node]): List[Node] = { - val numIncoming = mutable.Map[ComponentId, Int]().withDefaultValue(0) - - def forEachUsedComponent(nodes: Traversable[Node])(f: Node => Unit) { - nodes.foreach(_.usedComponents.foreach(f)) - } - - def partitionByNoIncoming(nodes: List[Node]) = - nodes.partition(node => numIncoming(node.componentId) == 0) - - @tailrec - def sort(sorted: List[Node], unsorted: List[Node]) : List[Node] = { - if (unsorted.isEmpty) { - sorted - } else { - val (ready, notReady) = partitionByNoIncoming(unsorted) - require(!ready.isEmpty, "There's a cycle in the graph.") //TODO: return cycle - forEachUsedComponent(ready) { injectedNode => numIncoming(injectedNode.componentId) -= 1} - sort(ready ::: sorted, notReady) - } - } - - forEachUsedComponent(nodes) { injectedNode => numIncoming(injectedNode.componentId) += 1 } - sort(List(), nodes) - } -} 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 deleted file mode 100644 index cc1745d6e35..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentNode.scala +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.{Constructor, InvocationTargetException, Modifier, ParameterizedType, Type} -import java.util.logging.Logger - -import com.google.inject.Inject -import com.yahoo.component.{AbstractComponent, ComponentId} -import com.yahoo.config.ConfigInstance -import com.yahoo.container.di.componentgraph.Provider -import com.yahoo.container.di.componentgraph.core.ComponentNode._ -import com.yahoo.container.di.componentgraph.core.Node.equalEdges -import com.yahoo.container.di.{ConfigKeyT, JavaAnnotation, createKey, makeClassCovariant, preserveStackTrace, removeStackTrace} -import com.yahoo.vespa.config.ConfigKey - -import scala.language.postfixOps - -/** - * @author Tony Vaagenes - * @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(ErrorOrComponentConstructorException(cutStackTraceAtConstructor(e.getCause), s"Error constructing $idAndType")) - } - - initId(instance) - } - - private def ErrorOrComponentConstructorException(cause: Throwable, message: String) : Throwable = { - if (cause != null && cause.isInstanceOf[Error]) // don't convert Errors to RuntimeExceptions - new Error(message, cause) - else - new ComponentConstructorException(message, cause) - } - - private def initId(component: AnyRef) = { - def checkAndSetId(c: AbstractComponent) { - if (c.hasInitializedId && c.getId != componentId ) - throw new IllegalStateException( - s"Component with id '$componentId' is trying to set its component id explicitly: '${c.getId}'. " + - "This is not allowed, so please remove any call to super() in your component's constructor.") - - 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 _ => throwComponentConstructorException("Multiple constructors annotated with inject in class " + clazz.getName) - } - } - - def constructorWithMostConfigParameters = { - def isConfigInstance(clazz: Class[_]) = classOf[ConfigInstance].isAssignableFrom(clazz) - - publicConstructors match { - case Array() => throwComponentConstructorException("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 throwComponentConstructorException(message: String) = - throw removeStackTrace(new ComponentConstructorException(message)) - - class ComponentConstructorException(message: String, cause: Throwable) extends RuntimeException(message, cause) { - def this(message: String) = this(message, null) - } - - def isAbstract(clazz: Class[_ <: AnyRef]) = Modifier.isAbstract(clazz.getModifiers) -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentRegistryNode.scala b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentRegistryNode.scala deleted file mode 100644 index 9c7e2ba322f..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/ComponentRegistryNode.scala +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di.componentgraph.core - -import com.yahoo.component.provider.ComponentRegistry -import com.yahoo.component.{ComponentId, Component} -import ComponentRegistryNode._ -import com.google.inject.Key -import com.google.inject.util.Types -import Node.syntheticComponentId - -/** - * @author Tony Vaagenes - * @author gjoranv - */ -class ComponentRegistryNode(val componentClass : Class[AnyRef]) - extends Node(componentId(componentClass)) { - - def usedComponents = componentsToInject - - protected def newInstance() = { - val registry = new ComponentRegistry[AnyRef] - - componentsToInject foreach { component => - registry.register(component.componentId, component.newOrCachedInstance()) - } - - registry - } - - override val instanceKey = - Key.get(Types.newParameterizedType(classOf[ComponentRegistry[_]], componentClass)).asInstanceOf[Key[AnyRef]] - - override val instanceType: Class[AnyRef] = instanceKey.getTypeLiteral.getRawType.asInstanceOf[Class[AnyRef]] - override val componentType: Class[AnyRef] = instanceType - - override def configKeys = Set() - - override def equals(other: Any) = { - other match { - case that: ComponentRegistryNode => - componentId == that.componentId && // includes componentClass - instanceType == that.instanceType && - equalEdges(usedComponents, that.usedComponents) - case _ => false - } - } - - override def label = - "{ComponentRegistry\\<%s\\>|%s}".format(componentClass.getSimpleName, Node.packageName(componentClass)) -} - -object ComponentRegistryNode { - val componentRegistryNamespace = ComponentId.fromString("ComponentRegistry") - - def componentId(componentClass: Class[_]) = { - syntheticComponentId(componentClass.getName, componentClass, componentRegistryNamespace) - } - - def equalEdges(edges: List[Node], otherEdges: List[Node]): Boolean = { - def compareEdges = { - (sortByComponentId(edges) zip sortByComponentId(otherEdges)). - forall(equalEdge) - } - - def sortByComponentId(in: List[Node]) = in.sortBy(_.componentId) - def equalEdge(edgePair: (Node, Node)): Boolean = edgePair._1.componentId == edgePair._2.componentId - - edges.size == otherEdges.size && - compareEdges - } -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/GuiceNode.scala b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/GuiceNode.scala deleted file mode 100644 index 26fb0e0d3d8..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/GuiceNode.scala +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di.componentgraph.core - -import com.yahoo.container.di.{JavaAnnotation, createKey} -import com.yahoo.component.ComponentId -import Node.syntheticComponentId -import GuiceNode._ - -/** - * @author Tony Vaagenes - * @author gjoranv - */ -final class GuiceNode(myInstance: AnyRef, - annotation: JavaAnnotation) extends Node(componentId(myInstance)) { - - override def configKeys = Set() - - override val instanceKey = createKey(myInstance.getClass, annotation) - override val instanceType = myInstance.getClass - override val componentType = instanceType - - - override def usedComponents = List() - - override protected def newInstance() = myInstance - - override def inject(component: Node) { - throw new UnsupportedOperationException("Illegal to inject components to a GuiceNode!") - } - - override def label = - "{{%s|Guice}|%s}".format(instanceType.getSimpleName, Node.packageName(instanceType)) -} - -object GuiceNode { - val guiceNamespace = ComponentId.fromString("Guice") - - def componentId(instance: AnyRef) = { - syntheticComponentId(instance.getClass.getName, instance, guiceNamespace) - } -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/JerseyNode.scala b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/JerseyNode.scala deleted file mode 100644 index 68353c47124..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/JerseyNode.scala +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.net.URL - -import com.yahoo.component.{ComponentId, ComponentSpecification} -import com.yahoo.container.di.Osgi -import com.yahoo.container.di.Osgi.RelativePath -import com.yahoo.container.di.componentgraph.core.JerseyNode._ -import com.yahoo.container.di.config.RestApiContext -import com.yahoo.container.di.config.RestApiContext.BundleInfo -import org.osgi.framework.Bundle -import org.osgi.framework.wiring.BundleWiring - -import scala.collection.JavaConverters._ - -/** - * Represents an instance of RestApiContext - * - * @author gjoranv - * @author Tony Vaagenes - */ -class JerseyNode(componentId: ComponentId, - override val configId: String, - clazz: Class[_ <: RestApiContext], - osgi: Osgi) - extends ComponentNode(componentId, configId, clazz) { - - - override protected def newInstance(): RestApiContext = { - val instance = super.newInstance() - val restApiContext = instance.asInstanceOf[RestApiContext] - - val bundles = restApiContext.bundlesConfig.bundles().asScala - bundles foreach ( bundleConfig => { - val bundleClasses = osgi.getBundleClasses( - ComponentSpecification.fromString(bundleConfig.spec()), - bundleConfig.packages().asScala.toSet) - - restApiContext.addBundle( - createBundleInfo(bundleClasses.bundle, bundleClasses.classEntries)) - }) - - componentsToInject foreach ( - component => - restApiContext.addInjectableComponent(component.instanceKey, component.componentId, component.newOrCachedInstance())) - - restApiContext - } - - override def equals(other: Any): Boolean = { - super.equals(other) && (other match { - case that: JerseyNode => componentsToInject == that.componentsToInject - case _ => false - }) - } - -} - -private[core] -object JerseyNode { - val WebInfUrl = "WebInfUrl" - - def createBundleInfo(bundle: Bundle, classEntries: Iterable[RelativePath]): BundleInfo = { - - val bundleInfo = new BundleInfo(bundle.getSymbolicName, - bundle.getVersion, - bundle.getLocation, - webInfUrl(bundle), - bundle.adapt(classOf[BundleWiring]).getClassLoader) - - bundleInfo.setClassEntries(classEntries.asJavaCollection) - bundleInfo - } - - - private def getBundle(osgi: Osgi, bundleSpec: String): Bundle = { - - val bundle = osgi.getBundle(ComponentSpecification.fromString(bundleSpec)) - if (bundle == null) - throw new IllegalArgumentException("Bundle not found: " + bundleSpec) - bundle - } - - private def webInfUrl(bundle: Bundle): URL = { - val strWebInfUrl = bundle.getHeaders.get(WebInfUrl) - - if (strWebInfUrl == null) null - else bundle.getEntry(strWebInfUrl) - } - -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/Node.scala b/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/Node.scala deleted file mode 100644 index d2476904e39..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/componentgraph/core/Node.scala +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright 2017 Yahoo Holdings. 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.util.logging.Logger - -import com.google.inject.Key -import com.yahoo.component.ComponentId -import com.yahoo.container.di.ConfigKeyT -import com.yahoo.container.di.componentgraph.Provider -import com.yahoo.container.di.componentgraph.core.Node._ -import com.yahoo.log.LogLevel.{DEBUG, SPAM} - -/** - * @author Tony Vaagenes - * @author gjoranv - */ -abstract class Node(val componentId: ComponentId) { - - def instanceKey: Key[AnyRef] - - var instance : Option[AnyRef] = None - - var componentsToInject = List[Node]() - - /** - * The components actually used by this node. - * Consist of a subset of the injected nodes + subset of the global nodes. - */ - def usedComponents: List[Node] - - protected def newInstance() : AnyRef - - def newOrCachedInstance() : AnyRef = { - val inst = if (instance.isEmpty) { - log.log(DEBUG, s"Creating new instance for component with ID $componentId") - instance = Some(newInstance()) - instance.get - } else { - log.log(SPAM, s"Reusing instance for component with ID $componentId") - instance.get - } - component(inst) - } - - private def component(instance: AnyRef) = instance match { - case provider: Provider[_] => provider.get().asInstanceOf[AnyRef] - case other => other - } - - def configKeys: Set[ConfigKeyT] - - def inject(component: Node) { - componentsToInject ::= component - } - - def injectAll(componentNodes: Traversable[ComponentNode]) { - componentNodes.foreach(inject(_)) - } - - def instanceType: Class[_ <: AnyRef] - def componentType: Class[_ <: AnyRef] - - override def equals(other: Any) = { - other match { - case that: Node => - getClass == that.getClass && - componentId == that.componentId && - instanceType == that.instanceType && - equalEdges(usedComponents, that.usedComponents) - case _ => false - } - } - - def label: String - - def idAndType = { - val className = instanceType.getName - - if (className == componentId.getName) s"'$componentId'" - else s"'$componentId' of type '$className'" - } - -} - -object Node { - private val log = Logger.getLogger(classOf[Node].getName) - - def equalEdges(edges1: List[AnyRef], edges2: List[AnyRef]): Boolean = { - def compare(objects: (AnyRef, AnyRef)): Boolean = { - objects match { - case (edge1: Node, edge2: Node) => equalEdge(edge1, edge2) - case (o1, o2) => o1 == o2 - } - } - - def equalEdge(e1: Node, e2: Node) = e1.componentId == e2.componentId - - (edges1 zip edges2).forall(compare) - } - - /** - * @param identityObject The identifying object that makes the Node unique - */ - private[componentgraph] - def syntheticComponentId(className: String, identityObject: AnyRef, namespace: ComponentId) = { - val name = className + "_" + System.identityHashCode(identityObject) - ComponentId.fromString(name).nestInNamespace(namespace) - } - - - def packageName(componentClass: Class[_]) = { - def nullIfNotFound(index : Int) = if (index == -1) 0 else index - - val fullClassName = componentClass.getName - fullClassName.substring(0, nullIfNotFound(fullClassName.lastIndexOf("."))) - } -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/osgi/OsgiUtil.scala b/container-di/src/main/scala/com/yahoo/container/di/osgi/OsgiUtil.scala deleted file mode 100644 index 3769eed6d2d..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/osgi/OsgiUtil.scala +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container.di.osgi - -import java.nio.file.{Files, Path, Paths} -import java.util.function.Predicate -import java.util.jar.{JarEntry, JarFile} -import java.util.logging.{Level, Logger} -import java.util.stream.Collectors - -import com.google.common.io.Files.fileTreeTraverser -import com.yahoo.component.ComponentSpecification -import com.yahoo.container.di.Osgi.RelativePath -import com.yahoo.osgi.maven.ProjectBundleClassPaths -import com.yahoo.osgi.maven.ProjectBundleClassPaths.BundleClasspathMapping -import org.osgi.framework.Bundle -import org.osgi.framework.wiring.BundleWiring - -import scala.collection.JavaConverters._ - -/** - * Tested by com.yahoo.application.container.jersey.JerseyTest - * @author Tony Vaagenes - */ -object OsgiUtil { - private val log = Logger.getLogger(getClass.getName) - private val classFileTypeSuffix = ".class" - - def getClassEntriesInBundleClassPath(bundle: Bundle, packagesToScan: Set[String]) = { - val bundleWiring = bundle.adapt(classOf[BundleWiring]) - - def listClasses(path: String, recurse: Boolean): Iterable[RelativePath] = { - val options = - if (recurse) BundleWiring.LISTRESOURCES_LOCAL | BundleWiring.LISTRESOURCES_RECURSE - else BundleWiring.LISTRESOURCES_LOCAL - - bundleWiring.listResources(path, "*" + classFileTypeSuffix, options).asScala - } - - if (packagesToScan.isEmpty) listClasses("/", recurse = true) - else packagesToScan flatMap { packageName => listClasses(packageToPath(packageName), recurse = false) } - } - - def getClassEntriesForBundleUsingProjectClassPathMappings(classLoader: ClassLoader, - bundleSpec: ComponentSpecification, - packagesToScan: Set[String]) = { - classEntriesFrom( - bundleClassPathMapping(bundleSpec, classLoader).classPathElements.asScala.toList, - packagesToScan) - } - - private def bundleClassPathMapping(bundleSpec: ComponentSpecification, - classLoader: ClassLoader): BundleClasspathMapping = { - - val projectBundleClassPaths = loadProjectBundleClassPaths(classLoader) - - if (projectBundleClassPaths.mainBundle.bundleSymbolicName == bundleSpec.getName) { - projectBundleClassPaths.mainBundle - } else { - log.log(Level.WARNING, s"Dependencies of the bundle $bundleSpec will not be scanned. Please file a feature request if you need this" ) - matchingBundleClassPathMapping(bundleSpec, projectBundleClassPaths.providedDependencies.asScala.toList) - } - } - - def matchingBundleClassPathMapping(bundleSpec: ComponentSpecification, - providedBundlesClassPathMappings: List[BundleClasspathMapping]): BundleClasspathMapping = { - providedBundlesClassPathMappings. - find(_.bundleSymbolicName == bundleSpec.getName). - getOrElse(throw new RuntimeException("No such bundle: " + bundleSpec)) - } - - private def loadProjectBundleClassPaths(classLoader: ClassLoader): ProjectBundleClassPaths = { - val classPathMappingsFileLocation = classLoader.getResource(ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME) - if (classPathMappingsFileLocation == null) - throw new RuntimeException(s"Couldn't find ${ProjectBundleClassPaths.CLASSPATH_MAPPINGS_FILENAME} in the class path.") - - ProjectBundleClassPaths.load(Paths.get(classPathMappingsFileLocation.toURI)) - } - - private def classEntriesFrom(classPathEntries: List[String], packagesToScan: Set[String]): Iterable[RelativePath] = { - val packagePathsToScan = packagesToScan map packageToPath - - classPathEntries.flatMap { entry => - val path = Paths.get(entry) - if (Files.isDirectory(path)) classEntriesInPath(path, packagePathsToScan) - else if (Files.isRegularFile(path) && path.getFileName.toString.endsWith(".jar")) classEntriesInJar(path, packagePathsToScan) - else throw new RuntimeException("Unsupported path " + path + " in the class path") - } - } - - private def classEntriesInPath(rootPath: Path, packagePathsToScan: Traversable[String]): Traversable[RelativePath] = { - def relativePathToClass(pathToClass: Path): RelativePath = { - val relativePath = rootPath.relativize(pathToClass) - relativePath.toString - } - - val fileIterator = - if (packagePathsToScan.isEmpty) fileTreeTraverser().preOrderTraversal(rootPath.toFile).asScala - else packagePathsToScan.view flatMap { packagePath => fileTreeTraverser().children(rootPath.resolve(packagePath).toFile).asScala } - - for { - file <- fileIterator - if file.isFile - if file.getName.endsWith(classFileTypeSuffix) - } yield relativePathToClass(file.toPath) - } - - - private def classEntriesInJar(jarPath: Path, packagePathsToScan: Set[String]): Traversable[RelativePath] = { - def packagePath(name: String) = { - name.lastIndexOf('/') match { - case -1 => name - case n => name.substring(0, n) - } - } - - val acceptedPackage: Predicate[String] = - if (packagePathsToScan.isEmpty) (name: String) => true - else (name: String) => packagePathsToScan(packagePath(name)) - - var jarFile: JarFile = null - try { - jarFile = new JarFile(jarPath.toFile) - jarFile.stream(). - map[String] { entry: JarEntry => entry.getName}. - filter { name: String => name.endsWith(classFileTypeSuffix)}. - filter(acceptedPackage). - collect(Collectors.toList()). - asScala - } finally { - if (jarFile != null) jarFile.close() - } - } - - def packageToPath(packageName: String) = packageName.replaceAllLiterally(".", "/") - - implicit class JavaPredicate[T](f: T => Boolean) extends Predicate[T] { - override def test(t: T): Boolean = f(t) - } - - implicit class JavaFunction[T, R](f: T => R) extends java.util.function.Function[T, R] { - override def apply(t: T): R = f(t) - } -} diff --git a/container-di/src/main/scala/com/yahoo/container/di/package.scala b/container-di/src/main/scala/com/yahoo/container/di/package.scala deleted file mode 100644 index cccb0242e8b..00000000000 --- a/container-di/src/main/scala/com/yahoo/container/di/package.scala +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.container - -import java.lang.reflect.Type - -import com.google.inject.Key -import com.yahoo.config.ConfigInstance -import com.yahoo.vespa.config.ConfigKey - -import scala.language.implicitConversions - -/** - * - * @author gjoranv - * @author Tony Vaagenes - */ -package object di { - type ConfigKeyT = ConfigKey[_ <: ConfigInstance] - type GuiceInjector = com.google.inject.Injector - type JavaAnnotation = java.lang.annotation.Annotation - - def createKey(instanceType: Type, annotation: JavaAnnotation) = { - {if (annotation == null) - Key.get(instanceType) - else - Key.get(instanceType, annotation) - }.asInstanceOf[Key[AnyRef]] - } - - implicit def makeClassCovariant[SUB, SUPER >: SUB](clazz: Class[SUB]) : Class[SUPER] = { - clazz.asInstanceOf[Class[SUPER]] - } - - def removeStackTrace(exception: Throwable): Throwable = { - if (preserveStackTrace) exception - else { - exception.setStackTrace(Array()) - exception - } - } - - //For debug purposes only - val preserveStackTrace: Boolean = Option(System.getProperty("jdisc.container.preserveStackTrace")).filterNot(_.isEmpty).isDefined -} |