Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for resolvers with a custom protocol handler #29

Merged
merged 11 commits into from
Mar 11, 2024
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")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this because it was failing on windows due to \ in the file path

.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")
Loading