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

net.adamcin.snagjar.SnagSession.scala Maven / Gradle / Ivy

/*
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 *
 * For more information, please refer to 
 */

package net.adamcin.snagjar

import java.io.{InputStream, FileFilter, File}
import java.util.jar.{JarFile, JarEntry}
import scalax.io.{Resource, CloseAction}
import java.util.{Collections, Properties}
import collection.JavaConversions._
import org.codehaus.plexus.util.SelectorUtils
import org.slf4j.{Logger, LoggerFactory}
import org.apache.maven.model.io.xpp3.MavenXpp3Reader
import org.apache.maven.model.{Dependency, Model}

/**
 * Class that performs the directory scanning and artifact snagging
 * @param filter basic globbing filter to be matched against maven coordinates (groupId:artifactId:version)
 * @param indexFile file to which the snagged maven coordinates will be written
 * @param snagFile file representing the directory or single file to be snagged during this session
 * @param recursive whether to recurse into a snagFile directory
 * @since 0.8.0
 * @author Mark Adamcin
 */
class SnagSession(val filter: String,
                  val indexFile: File,
                  val snagFile: File,
                  val recursive: Boolean) {

  val log = LoggerFactory.getLogger(getClass)

  lazy val tmpDir: File = {
    val tmp = File.createTempFile(getClass.getSimpleName, "")
    tmp.delete()
    new File(tmp.getParentFile, tmp.getName + ".dir")
  }

  val filterGav = (gav: GAV) => SelectorUtils.`match`(filter, gav.toString, true)

  def deleteRecursively(file: File) {
    if (file.isDirectory) {
      file.listFiles() foreach { deleteRecursively }
    }
    file.delete()
  }

  def createTempFile: File = {
    tmpDir.mkdir()
    File.createTempFile("snag", ".tmp", tmpDir)
  }

  def close() { deleteRecursively(tmpDir) }

  val indexRes = Resource.fromFile(indexFile)
  def filterTeeIndex(s: Snaggable): Boolean = {
    indexRes.write(s.gav.toString + "\n")
    true
  }

  def findArtifacts: Stream[Snaggable] = Option(snagFile) match {
    case Some(file) => streamFromFile(file).filter { s => filterGav(s.gav) }.filter { filterTeeIndex }
    case None => Stream.empty[Snaggable]
  }

  private def streamFromFile(file: File): Stream[Snaggable] = {
    if (!file.isDirectory) {
      streamFromJar(file)
    } else {
      streamFromDir(file)
    }
  }

  private def streamFromFiles(files: Stream[File]): Stream[Snaggable] = {
    files match {
      case head #:: tail =>
        streamFromFile(head) #::: streamFromFiles(tail)
      case _ => Stream.empty[Snaggable]
    }
  }

  private def streamFromJar(jar: File): Stream[Snaggable] = {
    Option(Snaggable(jar, this)) match {
      case Some(snaggable) => Stream(snaggable)
      case None => Stream.empty[Snaggable]
    }
  }

  private def streamFromDir(dir: File): Stream[Snaggable] =
    streamFromFiles(dir.listFiles(SnagSession.DIR_FILTER).toStream.filter((f: File) => recursive || !f.isDirectory))
}

object SnagSession {
  val log: Logger = LoggerFactory.getLogger(getClass)

  val EMBEDDED_PREFIX = "embedded"
  val JAR_SUFFIX = ".jar"

  val DIR_FILTER = new FileFilter {
    def accept(pathname: File) =
      pathname.isDirectory || pathname.getName.endsWith(JAR_SUFFIX)
  }

  val METADATA_PREFIX = "META-INF/maven/"
  val METADATA_SUFFIX = "/pom.properties"
  val POM_SUFFIX = "/pom.xml"

  val PROP_GROUP_ID = "groupId"
  val PROP_ARTIFACT_ID = "artifactId"
  val PROP_VERSION = "version"

  val metaFilter = (je: JarEntry) => je.getName.startsWith(METADATA_PREFIX) && je.getName.endsWith(METADATA_SUFFIX)
  val pomFilter = (je: JarEntry) => je.getName.startsWith(METADATA_PREFIX) && je.getName.endsWith(POM_SUFFIX)

  val inputCloser = new CloseAction[InputStream] {
    protected def closeImpl(resource: InputStream) =
      try {
        resource.close()
        Nil
      } catch {
        case ex: Throwable => List(ex)
      }
  }

  def jarEntryOpener(jar: JarFile)(entry: JarEntry) = jar.getInputStream(entry)

  val readGAV = (stream: (InputStream)) => {
    val props = new Properties()

    props.load(stream)

    (Option(props.getProperty(PROP_GROUP_ID)),
      Option(props.getProperty(PROP_ARTIFACT_ID)),
      Option(props.getProperty(PROP_VERSION))) match {

      case (Some(groupId), Some(artifactId), Some(version)) => GAV(groupId, artifactId, version)
      case _ => null
    }
  }

  def propsPathToPomPath(propsPath: String): String =
    propsPath.substring(0, propsPath.length - METADATA_SUFFIX.length) + POM_SUFFIX

  // TODO should return Option[Snaggable] instead and use flatMap on the resulting sequence
  def extract(file: File, session: SnagSession): Snaggable = {
    val jar = new JarFile(file)
    val opener = jarEntryOpener(jar)_

    val embeddedMetas = jar.entries().filter(metaFilter)

    val extractedMetas = embeddedMetas.map((metaEntry: JarEntry) => {
      val pomEntry = jar.getJarEntry(propsPathToPomPath(metaEntry.getName))
      val gav =
        Resource.fromInputStream(opener(metaEntry)).
          addCloseAction(inputCloser).acquireAndGet(readGAV)

      (Option(gav), Option(pomEntry)) match {
        case (Some(meta), Some(je)) => {
          val pom = session.createTempFile
          Resource.fromFile(pom).doCopyFrom(Resource.fromInputStream(opener(pomEntry)))
          (meta, pom)
        }
        case _ => null
      }
    }).toList

    // partial function application FTW!!!
    val extractor = extractDependencies(new MavenXpp3Reader)_

    val allDeps: Set[GAV] = extractedMetas.filter {

      _ match {
        case (gav: GAV, pom: File) => true
        case _ => false
      }

    }.map(extractor).foldLeft(List.empty[GAV]) {

      // fold left to build a combined set of all dependencies' GAVs
      (list, triple) => triple._3 ::: list

    }.toSet

    extractedMetas.filterNot { meta: (GAV, File) => allDeps contains meta._1 } match {
      case (gav, pom) :: Nil => new Snaggable(session, gav, file, pom)
      case _ => null
    }
  }

  def applyReader(modelReader: MavenXpp3Reader)(in: InputStream): Model = modelReader.read(in)

  def extractDependencies(modelReader: MavenXpp3Reader)(extractedMeta: (GAV, File)): (GAV, File, List[GAV]) =
    extractedMeta match {
      case (gav: GAV, pom: File) => {

        val modelIn = Resource.fromFile(pom).inputStream acquireFor { applyReader(modelReader)_ }

        val deps = modelIn match {
          case Right(model) =>

            val depIt = Option(model.getDependencies).getOrElse(Collections.emptyList[Dependency])

            depIt.map { dep => GAV(dep.getGroupId, dep.getArtifactId, dep.getVersion) }.toList

          case Left(exs) => List.empty[GAV] // TODO: Handle exceptions
        }

        (gav, pom, deps) // return triple
      }
      case _ => null
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy