Skip to content

Commit

Permalink
Use FS2 in test classpath resource loading
Browse files Browse the repository at this point in the history
  • Loading branch information
shinyhappydan committed Nov 21, 2023
1 parent 5468ba7 commit 4dcef08
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 27 deletions.
3 changes: 3 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ lazy val kernel = project
caffeine,
catsCore,
catsRetry,
catsEffect,
fs2,
fs2io,
circeCore,
circeParser,
handleBars,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
package ch.epfl.bluebrain.nexus.delta.kernel.utils

import cats.effect.IO
import cats.effect.{IO, Resource}
import cats.implicits.catsSyntaxMonadError
import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClasspathResourceError.{InvalidJson, InvalidJsonObject, ResourcePathNotFound}
import ch.epfl.bluebrain.nexus.delta.kernel.utils.ClasspathResourceLoader.handleBars
import com.github.jknack.handlebars.{EscapingStrategy, Handlebars}
import fs2.text
import io.circe.parser.parse
import io.circe.{Json, JsonObject}

import java.io.InputStream
import java.io.{IOException, InputStream}
import java.util.Properties
import scala.io.{Codec, Source}
import scala.jdk.CollectionConverters._

class ClasspathResourceLoader private (classLoader: ClassLoader) {

final def absolutePath(resourcePath: String): IO[String] =
IO.fromOption(Option(getClass.getResource(resourcePath)) orElse Option(classLoader.getResource(resourcePath)))(
ResourcePathNotFound(resourcePath)
).map(_.getPath)
final def absolutePath(resourcePath: String): IO[String] = {
IO.blocking(
Option(getClass.getResource(resourcePath))
.orElse(Option(classLoader.getResource(resourcePath)))
.toRight(ResourcePathNotFound(resourcePath))
).rethrow
.map(_.getPath)
}

/**
* Loads the content of the argument classpath resource as an [[InputStream]].
Expand All @@ -28,12 +33,15 @@ class ClasspathResourceLoader private (classLoader: ClassLoader) {
* the content of the referenced resource as an [[InputStream]] or a [[ClasspathResourceError]] when the resource
* is not found
*/
def streamOf(resourcePath: String): IO[InputStream] =
IO.defer {
lazy val fromClass = Option(getClass.getResourceAsStream(resourcePath))
val fromClassLoader = Option(classLoader.getResourceAsStream(resourcePath))
IO.fromOption(fromClass orElse fromClassLoader)(ResourcePathNotFound(resourcePath))
}
def streamOf(resourcePath: String): Resource[IO, InputStream] = {
Resource.make[IO, InputStream] {
IO.blocking {
Option(getClass.getResourceAsStream(resourcePath))
.orElse(Option(classLoader.getResourceAsStream(resourcePath)))
.toRight(ResourcePathNotFound(resourcePath))
}.rethrow
} { is => IO.blocking(is.close()) }
}

/**
* Loads the content of the argument classpath resource as a string and replaces all the key matches of the
Expand All @@ -48,11 +56,12 @@ class ClasspathResourceLoader private (classLoader: ClassLoader) {
final def contentOf(
resourcePath: String,
attributes: (String, Any)*
): IO[String] =
): IO[String] = {
resourceAsTextFrom(resourcePath).map {
case text if attributes.isEmpty => text
case text => handleBars.compileInline(text).apply(attributes.toMap.asJava)
}
}

/**
* Loads the content of the argument classpath resource as a java Properties and transforms it into a Map of key
Expand All @@ -65,10 +74,12 @@ class ClasspathResourceLoader private (classLoader: ClassLoader) {
* is not found
*/
final def propertiesOf(resourcePath: String): IO[Map[String, String]] =
streamOf(resourcePath).map { is =>
val props = new Properties()
props.load(is)
props.asScala.toMap
streamOf(resourcePath).use { is =>
IO.blocking {
val props = new Properties()
props.load(is)
props.asScala.toMap
}
}

/**
Expand Down Expand Up @@ -106,8 +117,17 @@ class ClasspathResourceLoader private (classLoader: ClassLoader) {
jsonObj <- IO.fromOption(json.asObject)(InvalidJsonObject(resourcePath))
} yield jsonObj

private def resourceAsTextFrom(resourcePath: String): IO[String] =
streamOf(resourcePath).map(is => Source.fromInputStream(is)(Codec.UTF8).mkString)
private def resourceAsTextFrom(resourcePath: String): IO[String] = {
fs2.io
.readClassLoaderResource[IO](resourcePath, classLoader = classLoader)
.through(text.utf8.decode)
.compile
.string
.adaptError {
case e: IOException if Option(e.getMessage).exists(_.endsWith("not found")) =>
ResourcePathNotFound(resourcePath)
}
}
}

object ClasspathResourceLoader {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import org.scalatest.concurrent.ScalaFutures
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpecLike

class ClasspathResourceUtilsSpec extends AnyWordSpecLike with Matchers with ScalaFutures {
class ClasspathResourceLoaderSpec extends AnyWordSpecLike with Matchers with ScalaFutures {
private val loader: ClasspathResourceLoader = ClasspathResourceLoader()

private def accept[A](io: IO[A]): A =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.topbraid.shacl.engine.ShapesGraph
import org.topbraid.shacl.util.SHACLUtil
import org.topbraid.shacl.validation.ValidationUtil

import java.io.InputStream
import java.net.URI

/**
Expand All @@ -31,12 +32,16 @@ object ShaclShapesGraph {
def shaclShaclShapes: IO[ShaclShapesGraph] =
loader
.streamOf("shacl-shacl.ttl")
.map { is =>
val model = ModelFactory
.createModelForGraph(createDefaultGraph())
.read(is, "http://www.w3.org/ns/shacl-shacl#", FileUtils.langTurtle)
validateAndRegister(model)
}
.use(readModel)
.map(model => validateAndRegister(model))

private def readModel(is: InputStream) = {
IO.blocking {
ModelFactory
.createModelForGraph(createDefaultGraph())
.read(is, "http://www.w3.org/ns/shacl-shacl#", FileUtils.langTurtle)
}
}

/**
* Creates a [[ShaclShapesGraph]] initializing and registering the required validation components from the passed
Expand Down

0 comments on commit 4dcef08

Please sign in to comment.