Skip to content

Commit

Permalink
Merge pull request #29 from rolang/add-custom-protocol-support
Browse files Browse the repository at this point in the history
Add support for resolvers with a custom protocol handler
  • Loading branch information
aiyanbo authored Mar 11, 2024
2 parents 7928c7e + 9554289 commit 9a7aab0
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 33 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ lib_managed/
src_managed/
project/boot/
project/plugins/project/
project/metals.sbt
project/project

# Scala-IDE specific
.scala_dependencies
.worksheet
.idea

.bsp
.bloop
.metals
.vscode
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ inThisBuild(List(
"[email protected]",
url("https://aiyanbo.github.io/")
)
)
),
Test / fork := true
))

coverageScalacPluginVersion := "2.0.10"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@ import org.apache.ivy.util.{CopyProgressEvent, CopyProgressListener}
import org.apache.maven.artifact.versioning.ArtifactVersion
import org.jmotor.sbt.artifact.exception.ArtifactNotFoundException

import java.net.URL
import java.net.{URL, URI}
import java.nio.file.{Files, Path, Paths}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.io.Source
import java.io.ByteArrayInputStream
import java.net.HttpURLConnection
import java.io.FileOutputStream

/**
* Component: Description: Date: 2018/2/8
Expand All @@ -23,33 +27,35 @@ trait MetadataLoader {
attrs: Map[String, String] = Map.empty
): Future[Seq[ArtifactVersion]]

def download(organization: String, artifactId: String, url: String)(implicit ec: ExecutionContext): Future[Path] = {
val src = new URL(url)
val dispatcher = URLHandlerRegistry.getDefault
def download(organization: String, artifactId: String, url: String)(implicit ec: ExecutionContext): Future[Path] =
Future {
dispatcher.getURLInfo(src)
}.flatMap {
case info if info.isReachable =>
val promise = Promise[Path]
val path = Files.createTempFile(s"maven-metadata-$organization-$artifactId", ".xml")
try {
dispatcher.download(
src,
path.toFile,
new CopyProgressListener {
override def start(evt: CopyProgressEvent): Unit = {}
try {
val src = new URI(url).toURL()
val connection = src.openConnection()

override def progress(evt: CopyProgressEvent): Unit = {}
Option(connection.getInputStream()).map { is =>
val path = Files.createTempFile(s"maven-metadata-$organization-$artifactId", ".xml")
val os = new FileOutputStream(path.toAbsolutePath.toString())

override def end(evt: CopyProgressEvent): Unit =
promise.success(path)
try {
val buffer = new Array[Byte](8192)
var bytesRead = is.read(buffer)
while (bytesRead >= 0) {
os.write(buffer, 0, bytesRead)
bytesRead = is.read(buffer)
}
)
} catch {
case e: Throwable => promise.failure(e)
path
} finally {
is.close()
os.close()
}
}
promise.future
case _ => throw ArtifactNotFoundException(organization, artifactId)
} catch {
case e: java.io.FileNotFoundException => None
case e: Throwable => throw e
}
}.flatMap {
case None => Future.failed(ArtifactNotFoundException(organization, artifactId))
case Some(path) => Future.successful(path)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.apache.maven.artifact.versioning.{ArtifactVersion, DefaultArtifactVer
import org.jmotor.sbt.artifact.exception.ArtifactNotFoundException
import org.jmotor.sbt.artifact.metadata.MetadataLoader

import java.net.URL
import java.net.URI
import java.nio.file.{Files, Paths}
import scala.concurrent.{ExecutionContext, Future}
import scala.xml.XML
Expand All @@ -18,17 +18,18 @@ import scala.xml.XML
class MavenRepoMetadataLoader(url: String)(implicit ec: ExecutionContext) extends MetadataLoader {

private[this] lazy val (protocol, base) = {
val u = new URL(url)
u.getProtocol + "://" -> url.replace(s"${u.getProtocol}://", "")
val u = new URI(url)
(u.getScheme + "://" -> u.getRawSchemeSpecificPart.stripPrefix("//"))
}

override def getVersions(
organization: String,
artifactId: String,
attrs: Map[String, String]
): Future[Seq[ArtifactVersion]] = {
val location =
protocol + Paths.get(base, organization.split('.').mkString("/"), artifactId, "maven-metadata.xml").toString
val location = new URI(s"$protocol$base/${organization.split('.').mkString("/")}/$artifactId/maven-metadata.xml")
.normalize()
.toString()
download(organization, artifactId, location).map { file =>
val stream = Files.newInputStream(file)
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import sbt.util.Logger
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.control.NonFatal
import scala.util.{Failure, Success}

/**
* Component: Description: Date: 2018/2/9
Expand Down Expand Up @@ -85,7 +86,10 @@ class VersionServiceImpl(logger: Logger, scalaVersion: String, scalaBinaryVersio
case repo: MavenRepository =>
val url = repo.root
if (isRemote(url)) {
Option(new MavenRepoMetadataLoader(url))
scala.util.Try(new java.net.URI(url).toURL()) match {
case Failure(e) => logger.err(s"""Invalid URL "$url" for Maven repository: ${e.getMessage}"""); None
case Success(_) => Option(new MavenRepoMetadataLoader(url))
}
} else {
None
}
Expand All @@ -106,6 +110,6 @@ class VersionServiceImpl(logger: Logger, scalaVersion: String, scalaBinaryVersio
}

private[this] def isRemote(url: String): Boolean =
url.startsWith("http://") || url.startsWith("https://")
!url.startsWith("file:") && !url.startsWith("jar:")

}
31 changes: 31 additions & 0 deletions src/test/scala/org/jmotor/sbt/service/VersionServiceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import sbt.util.Logger

import scala.concurrent.Await
import scala.concurrent.duration.*
import java.net.{URI, URL, URLConnection, URLStreamHandler, URLStreamHandlerFactory}
import java.util.concurrent.atomic.AtomicReference

/** Component: Description: Date: 2018/3/1
*
Expand Down Expand Up @@ -57,4 +59,33 @@ class VersionServiceSpec extends AnyFunSuite {
assert(status.status == Status.Expired)
}

test("uses custom protocol handlers") {
val downloadsCalled = new AtomicReference(Vector.empty[String])

// setting stream handler can be executed only once on jvm
// to be able to execute it multiple times from sbt shell it requires forking enabled like "Test / fork := true"
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory {
def createURLStreamHandler(protocol: String): URLStreamHandler = protocol match {
case "artifactregistry" =>
new URLStreamHandler {
protected def openConnection(url: URL): URLConnection = {
downloadsCalled.getAndUpdate(_ :+ url.toString())
new URI(s"https://${url.getHost}${url.getPath()}").normalize.toURL.openConnection
}
}
case _ => null
}
})

val testResolver = MavenRepo("m2", "artifactregistry://repo1.maven.org/maven2/")
val versionService = VersionService(Logger.Null, "2.12.4", "2.12", Seq(testResolver), Seq.empty)
Await.result(versionService.checkForUpdates(ModuleID("com.google.guava", "guava", "23.0-jre")), 30.seconds)

assert(
downloadsCalled
.get()
.contains("artifactregistry://repo1.maven.org/maven2/com/google/guava/guava/maven-metadata.xml")
)
}

}
2 changes: 1 addition & 1 deletion version.sbt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
ThisBuild / version := "1.2.8"
ThisBuild / version := "1.2.9-SNAPSHOT"

ThisBuild / versionScheme := Some("semver-spec")

0 comments on commit 9a7aab0

Please sign in to comment.