diff --git a/.gitignore b/.gitignore index 33c6b38..60cfdd3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ lib_managed/ src_managed/ project/boot/ project/plugins/project/ +project/metals.sbt +project/project # Scala-IDE specific .scala_dependencies @@ -18,3 +20,6 @@ project/plugins/project/ .idea .bsp +.bloop +.metals +.vscode \ No newline at end of file diff --git a/build.sbt b/build.sbt index 3521ed8..4717da5 100644 --- a/build.sbt +++ b/build.sbt @@ -16,7 +16,8 @@ inThisBuild(List( "yanbo.ai@gmail.com", url("https://aiyanbo.github.io/") ) - ) + ), + Test / fork := true )) coverageScalacPluginVersion := "2.0.10" diff --git a/src/main/scala/org/jmotor/sbt/artifact/metadata/MetadataLoader.scala b/src/main/scala/org/jmotor/sbt/artifact/metadata/MetadataLoader.scala index e912c80..e45b14c 100644 --- a/src/main/scala/org/jmotor/sbt/artifact/metadata/MetadataLoader.scala +++ b/src/main/scala/org/jmotor/sbt/artifact/metadata/MetadataLoader.scala @@ -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 @@ -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) } - } } diff --git a/src/main/scala/org/jmotor/sbt/artifact/metadata/loader/MavenRepoMetadataLoader.scala b/src/main/scala/org/jmotor/sbt/artifact/metadata/loader/MavenRepoMetadataLoader.scala index 4709073..b802172 100644 --- a/src/main/scala/org/jmotor/sbt/artifact/metadata/loader/MavenRepoMetadataLoader.scala +++ b/src/main/scala/org/jmotor/sbt/artifact/metadata/loader/MavenRepoMetadataLoader.scala @@ -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 @@ -18,8 +18,8 @@ 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( @@ -27,8 +27,9 @@ class MavenRepoMetadataLoader(url: String)(implicit ec: ExecutionContext) extend 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 { diff --git a/src/main/scala/org/jmotor/sbt/service/VersionServiceImpl.scala b/src/main/scala/org/jmotor/sbt/service/VersionServiceImpl.scala index 454b788..f29e440 100644 --- a/src/main/scala/org/jmotor/sbt/service/VersionServiceImpl.scala +++ b/src/main/scala/org/jmotor/sbt/service/VersionServiceImpl.scala @@ -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 @@ -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 } @@ -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:") } diff --git a/src/test/scala/org/jmotor/sbt/service/VersionServiceSpec.scala b/src/test/scala/org/jmotor/sbt/service/VersionServiceSpec.scala index d03a0b2..cdd7b45 100644 --- a/src/test/scala/org/jmotor/sbt/service/VersionServiceSpec.scala +++ b/src/test/scala/org/jmotor/sbt/service/VersionServiceSpec.scala @@ -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 * @@ -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") + ) + } + } diff --git a/version.sbt b/version.sbt index b15e398..1ef27f2 100644 --- a/version.sbt +++ b/version.sbt @@ -1,3 +1,3 @@ -ThisBuild / version := "1.2.8" +ThisBuild / version := "1.2.9-SNAPSHOT" ThisBuild / versionScheme := Some("semver-spec")