All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.twitter.finagle.util.LoadService.scala Maven / Gradle / Ivy

The newest version!
package com.twitter.finagle.util

import com.twitter.logging.Level
import java.io.{IOException, File, InputStream}
import java.net.{URI, URLClassLoader, URISyntaxException}
import java.util.ServiceConfigurationError
import java.util.jar.JarFile
import scala.collection.JavaConverters._
import scala.collection.mutable
import scala.io.Source

/**
 * Inspect and load the classpath. Inspired by Guava's ClassPath
 * utility.
 *
 * @note This is not a generic facility, rather it is designed
 * specifically to support LoadService.
 */
private object ClassPath {

  private val ignoredPackages = Seq(
    "apple/", "ch/epfl/", "com/apple/", "com/oracle/", 
    "com/sun/", "java/", "javax/", "scala/", "sun/", "sunw/")

  /**
   * Information about a classpath entry.
   */
  case class Info(path: String, iface: String, lines: Seq[String])

  /**
   * Browse the given classloader recursively from
   * its package root.
   */
  def browse(loader: ClassLoader): Seq[Info] = {
    val buf = mutable.Buffer[Info]()

    for ((uri, loader) <- getEntries(loader))
      browseUri(uri, loader, buf)

    buf
  }

  private def ifaceOfName(name: String) =
    if (!(name contains "META-INF")) None
    else name.split("/").takeRight(3) match {
      case Array("META-INF", "services", iface) => Some(iface)
      case _ => None
    }

  private def getEntries(loader: ClassLoader): Seq[(URI, ClassLoader)] = {
    val ents = mutable.Buffer[(URI, ClassLoader)]()
    val parent = loader.getParent()
    if (parent != null)
      ents ++= getEntries(parent)

    loader match {
      case urlLoader: URLClassLoader =>
        for (url <- urlLoader.getURLs()) {
          ents += (url.toURI() -> loader)
        }
      case _ =>
    }
    
    ents
  }

  private[finagle] def browseUri(uri: URI, loader: ClassLoader, buf: mutable.Buffer[Info]) {
    if (uri.getScheme != "file")
      return
    val f = new File(uri)
    if (!(f.exists() && f.canRead()))
      return

    if (f.isDirectory)
      browseDir(f, loader, "", buf)
    else
      browseJar(f, loader, buf)
  }

  private def browseDir(
      dir: File, loader: ClassLoader, 
      prefix: String, buf: mutable.Buffer[Info]) {
    if (ignoredPackages contains prefix)
      return

    for (f <- dir.listFiles)
      if (f.isDirectory() && f.canRead())
        browseDir(f, loader, prefix + f.getName + "/", buf)
      else for (iface <- ifaceOfName(prefix + f.getName)) {
        val source = Source.fromFile(f, "UTF-8")
        val lines = source.getLines().toList
        source.close()
        buf += Info(prefix + f.getName, iface, lines)
      }
  }

  private def browseJar(file: File, loader: ClassLoader, buf: mutable.Buffer[Info]) {
    val jarFile = try new JarFile(file) catch {
      case _: IOException => return  // not a Jar file
    }

    try {
      for (uri <- jarClasspath(file, jarFile.getManifest))
        browseUri(uri, loader, buf)

      for {
        e <- jarFile.entries.asScala
        if !e.isDirectory
        n = e.getName
        if !(ignoredPackages exists (n startsWith _))
        iface <- ifaceOfName(n)
      } {
        val source = Source.fromInputStream(jarFile.getInputStream(e))
        val lines = source.getLines().toList
        source.close()
        buf += Info(n, iface, lines)
      }
    } finally {
      try jarFile.close() catch {
        case _: IOException =>
      }
    }
  }

  private def jarClasspath(jarFile: File, manifest: java.util.jar.Manifest): Seq[URI] = for {
    m <- Option(manifest).toSeq
    attr <- Option(m.getMainAttributes().getValue("Class-Path")).toSeq
    el <- attr.split(" ")
    uri <- uriFromJarClasspath(jarFile, el)
  } yield uri

  private def uriFromJarClasspath(jarFile: File, path: String) = try {
    val uri = new URI(path)
    if (uri.isAbsolute)
      Some(uri)
    else
      Some(new File(jarFile.getParentFile, path.replace('/', File.separatorChar)).toURI)
  } catch {
    case _: URISyntaxException => None
  }
}

/**
 * Load a singleton class in the manner of
 * [[java.util.ServiceLoader]]. It is more resilient to varying Java
 * packaging configurations than ServiceLoader.
 */
object LoadService {

  def apply[T: ClassManifest](): Seq[T] = {
    val iface = implicitly[ClassManifest[T]].erasure.asInstanceOf[Class[T]]

    val classesOfIface = {
      val loader = iface.getClassLoader()
      val mappings = for {
        ClassPath.Info(_, iface, clss) <- ClassPath.browse(loader)
        cls <- clss
        if cls.nonEmpty
      } yield (iface -> cls)
    
      mappings.foldLeft(Map[String, Set[String]]()) {
        case (m, (iface, cls)) =>
          m + (iface -> (m.getOrElse(iface, Set()) + cls))
      }
    }

    if (!(classesOfIface contains iface.getName))
      return Seq.empty

    for (n <- classesOfIface(iface.getName).toSeq) yield {
      val cls = Class.forName(n)
      if (!(iface isAssignableFrom cls)) {
        throw new ServiceConfigurationError(
          ""+n+" not a subclass of "+iface.getName)
      }

      DefaultLogger.log(
        Level.DEBUG,
        "LoadService: loaded instance of class %s for requested service %s".format(n, iface.getName)
      )
      cls.newInstance().asInstanceOf[T]
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy