diff --git a/README.md b/README.md index c21b658..b2bf8cd 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,15 @@ A proof-of-concept implementation of a Kotlin-based JVM runner. Currently, this This runner includes a set of standard processors that can be used directly by the end user without the need for additional dependencies. These processors also serve as a reference for processor developers. The implementation can be found [here](src/main/kotlin/std). +##### RDF Utilities + +Interact with RDF data. + +| Processor | Description | +|-------------------------------------------------------------|--------------------------------| +| [`jvm:RDFValidator`](./src/main/kotlin/std/RDFValidator.kt) | Validate RDF data using SHACL. | + + ##### Network Utilities These processors interact with the network. @@ -20,7 +29,7 @@ These processors interact with the network. ##### File Utilities -These processors interact with the local file system. +Fetch and write data from and to the local file system. | Processor | Description | |---------------------------------|------------------------------------------------------------------------| diff --git a/src/main/kotlin/std/RDFValidator.kt b/src/main/kotlin/std/RDFValidator.kt new file mode 100644 index 0000000..7ba4dbf --- /dev/null +++ b/src/main/kotlin/std/RDFValidator.kt @@ -0,0 +1,75 @@ +package technology.idlab.std + +import bridge.Reader +import bridge.Writer +import java.io.ByteArrayOutputStream +import java.io.File +import org.apache.jena.graph.Graph +import org.apache.jena.rdf.model.ModelFactory +import org.apache.jena.shacl.ShaclValidator +import technology.idlab.extensions.readModelRecursively +import technology.idlab.logging.Log +import technology.idlab.runner.Processor + +class RDFValidator(args: Map) : Processor(args) { + /** Default values. */ + private val errorIsFatalDefault = false + private val printReportDefault = false + + /** Arguments. */ + private val errorIsFatal = this.getOptionalArgument("error_is_fatal") + private val printReport = this.getOptionalArgument("print_report") + private val input = this.getArgument("input") + private val output = this.getArgument("output") + + /** Runtime fields. */ + private val shapes: Graph + private val model = ModelFactory.createDefaultModel() + private val validator = ShaclValidator.get() + + // Initialize the shape graph and validator. + init { + val path = this.getArgument("shapes") + val file = File(path) + val shapesModel = file.readModelRecursively() + this.shapes = shapesModel.graph + } + + /** Read incoming data, validate it, and output it. */ + override fun exec() { + while (true) { + // Read incoming data. + val res = input.readSync() + if (res.isClosed()) { + break + } + + // Parse as a model. + Log.shared.assert(model.isEmpty, "Model should be empty.") + model.read(res.value.toString()) + + // Validate the model. + val report = validator.validate(shapes, model.graph) + if (!report.conforms()) { + if (printReport.orElse(printReportDefault)) { + val out = ByteArrayOutputStream() + report.model.write(out, "TURTLE") + Log.shared.info(out.toString()) + } + + if (errorIsFatal.orElse(errorIsFatalDefault)) { + Log.shared.fatal("Validation error is fatal.") + } + } + + // Reset model for next invocation. + model.removeAll(null, null, null) + + // Propagate to the output. + output.pushSync(res.value) + } + + // Close the output. + output.close() + } +} diff --git a/src/main/resources/std/rdf_validator.ttl b/src/main/resources/std/rdf_validator.ttl new file mode 100644 index 0000000..3b773c3 --- /dev/null +++ b/src/main/resources/std/rdf_validator.ttl @@ -0,0 +1,44 @@ +@prefix jvm: . +@prefix owl: . +@prefix sh: . +@prefix xsd: . + +<> owl:imports <../pipeline.ttl>. + +jvm:RDFValidator a jvm:Processor; + jvm:file <../../kotlin/std/RDFValidator.kt>; + jvm:language "Kotlin". + +[] a sh:NodeShape; + sh:targetClass jvm:RDFValidator; + sh:property [ + sh:path jvm:shapes; + sh:name "shapes"; + sh:datatype xsd:string; + sh:minCount 1; + sh:maxCount 1; + ], [ + sh:path jvm:error_is_fatal; + sh:name "error_is_fatal"; + sh:datatype xsd:boolean; + sh:maxCount 1; + ], [ + sh:path jvm:print_report; + sh:name "print_report"; + sh:datatype xsd:boolean; + sh:maxCount 1; + ], [ + sh:path jvm:input; + sh:name "input"; + sh:class jvm:ChannelReader; + sh:minCount 1; + sh:maxCount 1; + ], [ + sh:path jvm:output; + sh:name "output"; + sh:class jvm:ChannelWriter; + sh:minCount 1; + sh:maxCount 1; + ]; + sh:closed true; + sh:ignoredProperties (rdf:type).