From 1d98b9e8a397acf8b6f6f55a3fd5189eb72740ba Mon Sep 17 00:00:00 2001 From: Ivan Topolnjak Date: Sun, 18 Jun 2017 12:38:09 +0200 Subject: add DynamicAccess utility --- .../src/main/scala/kamon/ReporterRegistry.scala | 41 +++++++--------- .../src/main/scala/kamon/util/DynamicAccess.scala | 56 ++++++++++++++++++++++ 2 files changed, 73 insertions(+), 24 deletions(-) create mode 100644 kamon-core/src/main/scala/kamon/util/DynamicAccess.scala (limited to 'kamon-core/src/main/scala/kamon') diff --git a/kamon-core/src/main/scala/kamon/ReporterRegistry.scala b/kamon-core/src/main/scala/kamon/ReporterRegistry.scala index 6c8622d4..ae43ca3c 100644 --- a/kamon-core/src/main/scala/kamon/ReporterRegistry.scala +++ b/kamon-core/src/main/scala/kamon/ReporterRegistry.scala @@ -22,7 +22,7 @@ import java.util.concurrent._ import com.typesafe.config.Config import kamon.metric._ import kamon.trace.Span -import kamon.util.Registration +import kamon.util.{DynamicAccess, Registration} import org.slf4j.LoggerFactory import scala.concurrent.{ExecutionContext, ExecutionContextExecutorService, Future} @@ -42,19 +42,17 @@ trait ReporterRegistry { def stopAllReporters(): Future[Unit] } -trait MetricReporter { +sealed trait Reporter { def start(): Unit def stop(): Unit - def reconfigure(config: Config): Unit - def reportTickSnapshot(snapshot: TickSnapshot): Unit } -trait SpanReporter { - def start(): Unit - def stop(): Unit +trait MetricReporter extends Reporter { + def reportTickSnapshot(snapshot: TickSnapshot): Unit +} - def reconfigure(config: Config): Unit +trait SpanReporter extends Reporter { def reportSpans(spans: Seq[Span.CompletedSpan]): Unit } @@ -77,22 +75,17 @@ class ReporterRegistryImpl(metrics: MetricsSnapshotGenerator, initialConfig: Con logger.info("The kamon.reporters setting is empty, no reporters have been started.") else { registryConfiguration.configuredReporters.foreach { reporterFQCN => - Try { - val reporterClass = Class.forName(reporterFQCN) - val instance = reporterClass.newInstance() - instance match { - case mr: MetricReporter => - addMetricReporter(mr, "loaded-from-config: " + reporterFQCN) - logger.info("Loaded metric reporter [{}]", reporterFQCN) - - case sr: SpanReporter => - addSpanReporter(sr, "loaded-from-config: " + reporterFQCN) - logger.info("Loaded span reporter [{}]", reporterFQCN) - - case anyOther => - logger.error("Cannot add [{}] as a reporter, it doesn't implement the MetricReporter or SpanReporter interfaces", anyOther) - } - }.failed.foreach { + val dynamicAccess = new DynamicAccess(getClass.getClassLoader) + dynamicAccess.createInstanceFor[Reporter](reporterFQCN, Nil).map({ + case mr: MetricReporter => + addMetricReporter(mr, "loaded-from-config: " + reporterFQCN) + logger.info("Loaded metric reporter [{}]", reporterFQCN) + + case sr: SpanReporter => + addSpanReporter(sr, "loaded-from-config: " + reporterFQCN) + logger.info("Loaded span reporter [{}]", reporterFQCN) + + }).failed.foreach { t => logger.error(s"Failed to load configured reporter [$reporterFQCN]", t) } } diff --git a/kamon-core/src/main/scala/kamon/util/DynamicAccess.scala b/kamon-core/src/main/scala/kamon/util/DynamicAccess.scala new file mode 100644 index 00000000..87f7024a --- /dev/null +++ b/kamon-core/src/main/scala/kamon/util/DynamicAccess.scala @@ -0,0 +1,56 @@ +package kamon.util + +import scala.collection.immutable +import java.lang.reflect.InvocationTargetException +import scala.reflect.ClassTag +import scala.util.Try + +/** + * Utility class for creating instances from a FQCN, see [1] for the original source. + * + * It uses reflection to turn fully-qualified class names into `Class[_]` objects and creates instances from there + * using `getDeclaredConstructor()` and invoking that. + * + * [1] https://github.com/akka/akka/blob/master/akka-actor/src/main/scala/akka/actor/ReflectiveDynamicAccess.scala + */ +class DynamicAccess(val classLoader: ClassLoader) { + + def getClassFor[T: ClassTag](fqcn: String): Try[Class[_ <: T]] = + Try[Class[_ <: T]]({ + val c = Class.forName(fqcn, false, classLoader).asInstanceOf[Class[_ <: T]] + val t = implicitly[ClassTag[T]].runtimeClass + if (t.isAssignableFrom(c)) c else throw new ClassCastException(t + " is not assignable from " + c) + }) + + def createInstanceFor[T: ClassTag](clazz: Class[_], args: immutable.Seq[(Class[_], AnyRef)]): Try[T] = + Try { + val types = args.map(_._1).toArray + val values = args.map(_._2).toArray + val constructor = clazz.getDeclaredConstructor(types: _*) + constructor.setAccessible(true) + val obj = constructor.newInstance(values: _*) + val t = implicitly[ClassTag[T]].runtimeClass + if (t.isInstance(obj)) obj.asInstanceOf[T] else throw new ClassCastException(clazz.getName + " is not a subtype of " + t) + } recover { case i: InvocationTargetException if i.getTargetException ne null ⇒ throw i.getTargetException } + + def createInstanceFor[T: ClassTag](fqcn: String, args: immutable.Seq[(Class[_], AnyRef)]): Try[T] = + getClassFor(fqcn) flatMap { c ⇒ createInstanceFor(c, args) } + + def getObjectFor[T: ClassTag](fqcn: String): Try[T] = { + val classTry = + if (fqcn.endsWith("$")) getClassFor(fqcn) + else getClassFor(fqcn + "$") recoverWith { case _ ⇒ getClassFor(fqcn) } + classTry flatMap { c ⇒ + Try { + val module = c.getDeclaredField("MODULE$") + module.setAccessible(true) + val t = implicitly[ClassTag[T]].runtimeClass + module.get(null) match { + case null ⇒ throw new NullPointerException + case x if !t.isInstance(x) ⇒ throw new ClassCastException(fqcn + " is not a subtype of " + t) + case x: T ⇒ x + } + } recover { case i: InvocationTargetException if i.getTargetException ne null ⇒ throw i.getTargetException } + } + } +} \ No newline at end of file -- cgit v1.2.3