All Downloads are FREE. Search and download functionalities are using the official Maven repository.

es.weso.schema.ShaclexSchema.scala Maven / Gradle / Ivy

package es.weso.schema

import cats.implicits._
import es.weso.rdf._
import es.weso.rdf.nodes._
import es.weso.rdf.jena.RDFAsJenaModel
import es.weso.shacl.SHACLPrefixes.`owl:imports`
import es.weso.shacl.report.{AbstractResult, MsgError}
import es.weso.shacl.{Schema => ShaclSchema, _}
// import es.weso.shacl._
import es.weso.shacl.converter.{RDF2Shacl, Shacl2ShEx}
import es.weso.shacl.report.{ValidationReport, ValidationResult}
import es.weso.shacl.validator.{CheckResult, Evidence, ShapeTyping, Validator}
import es.weso.shapemaps._
import es.weso.utils.internal.CollectionCompat._
import util._
import es.weso.typing._
import es.weso.utils.MapUtils
//import cats.data.EitherT
import cats.effect._

case class ShaclexSchema(schema: ShaclSchema) extends Schema {
  override def name = "SHACLex"

  override def formats: Seq[String] = DataFormats.formatNames ++ Seq("TREE")

  override def defaultTriggerMode: ValidationTrigger = TargetDeclarations

  override def validate(rdf: RDFReader, trigger: ValidationTrigger, builder: RDFBuilder): IO[Result] = trigger match {
    case TargetDeclarations => validateTargetDecls(rdf).map(_.addTrigger(trigger))
    case _ => IO(Result.errStr(s"Not implemented trigger ${trigger.name} for SHACL yet"))
  }

  def validateTargetDecls(rdf: RDFReader): IO[Result] = {
    val validator = Validator(schema)
    RDFAsJenaModel.empty.flatMap(_.use(emptyRdf => for {
      r <- validator.validateAll(rdf)
      builder <- emptyRdf.addPrefixMap(schema.pm)
      result <-  cnvResult(r, rdf, builder)
    } yield result))
  }

  def cnvResult(r: CheckResult[AbstractResult, (ShapeTyping,Boolean), List[Evidence]],
                rdf: RDFReader,
                builder: RDFBuilder
               ): IO[Result] = {
    val vr: ValidationReport =
      r.result.fold(
        e => ValidationReport.fromError(e), r =>
        r._1.toValidationReport
      )
    for {
      pm <- rdf.getPrefixMap
    } yield Result(
      isValid = vr.conforms,
      message = if (vr.conforms) "Valid" else "Not valid",
      shapeMaps = r.results.map(cnvShapeTyping(_, rdf,pm)),
      validationReport = ShaclexReport(vr),
      errors = vr.results.map(cnvViolationError),
      trigger = None,
      nodesPrefixMap = pm,
      shapesPrefixMap = schema.pm)
  }
  
  def cnvShapeTyping(t: (ShapeTyping, Boolean), rdf: RDFReader, pm: PrefixMap): ResultShapeMap = {
    ResultShapeMap(
      mapValues(t._1.getMap)(cnvMapShapeResult), pm, schema.pm)
  }

  private def cnvMapShapeResult(m: scala.collection.Map[Shape, TypingResult[AbstractResult, String]]): Map[ShapeMapLabel, Info] = {

    MapUtils.cnvMap(m.toMap, cnvShape, cnvTypingResult)
  }

  private def cnvShape(s: Shape): ShapeMapLabel = {
    s.id match {
      case iri: IRI => IRILabel(iri)
      case bnode: BNode => BNodeLabel(bnode)
      case _ => throw new Exception(s"cnvShape: unexpected ${s.id}")
    }
  }

  private def cnvTypingResult(t: TypingResult[AbstractResult, String]): Info = {
    import showShacl._
    import TypingResult.showTypingResult
    Info(
      status = if (t.isOK) Conformant else NonConformant,
      reason = Some(t.show)
    // TODO: Convert typing result to JSON and add it to appInfo
    )
  }

  private def cnvViolationError(v: AbstractResult): ErrorInfo = {
    val pm = schema.pm
    v match {
      case ar: MsgError => ErrorInfo(s"Error: $ar")
      case vr: ValidationResult =>
        ErrorInfo(
          pm.qualify(vr.sourceConstraintComponent) +
            " FocusNode: " + schema.pm.qualify(vr.focusNode) + " " +
            vr.message.mkString(","))
    }
  }

  /*def validateShapeMap(sm: Map[RDFNode,Set[String]], nodesStart: Set[RDFNode], rdf: RDFReader) : Result = {
    throw new Exception("Unimplemented validateShapeMap")
  }*/

  override def fromString(str: String, format: String, 
     base: Option[String]
    ): IO[Schema] = {
    RDFAsJenaModel.fromString(str.toString, format, base.map(IRI(_))).flatMap(_.use(rdf => for {
      eitherSchema <- RDF2Shacl.getShacl(rdf, resolveImports = true).attempt
      schema <- eitherSchema match {
        case Left(s) => IO.raiseError(new RuntimeException(s))
        case Right(schema) => IO.pure(schema)
      }
    } yield ShaclexSchema(schema)))
  }

  // private def err[A](msg:String): EitherT[IO,String, A] = EitherT.leftT[IO,A](msg)

  override def fromRDF(rdf: RDFReader): IO[es.weso.schema.Schema] = for {
    eitherBuilder <- rdf.asRDFBuilder.attempt
    schema <- eitherBuilder match {
    case Left(_) => for {
      ts <- rdf.triplesWithPredicate(`owl:imports`).compile.toList
      schema <- ts.size match {
        case 0 => RDF2Shacl.getShaclReader(rdf).map(ShaclexSchema(_))
        case _ => IO.raiseError(new RuntimeException(s"fromRDF: Not supported owl:imports for this kind of RDF model\nRDFReader: $rdf"))
      }
    } yield schema

    case Right(rdfBuilder) =>
      for {
        maybeSchemaShacl <- RDF2Shacl.getShacl(rdfBuilder, resolveImports = true).attempt
        schemaShacl <- maybeSchemaShacl.fold(
          s => IO.raiseError(new RuntimeException(s)),
          s => IO.pure(s)
        )
      } yield {
        val ss: es.weso.schema.Schema = ShaclexSchema(schemaShacl)
        ss
      }
   }
  } yield schema

/*  private def handleErr[A](e: Either[String,A]): IO[A] = e.fold(
    s => IO.raiseError(new RuntimeException(s)),
    IO.pure
  ) */

  override def serialize(format: String, base: Option[IRI]): IO[String] = RDFAsJenaModel.empty.flatMap(_.use(builder => for {
    str <- if (formats.contains(format.toUpperCase))
      schema.serialize(format, base, builder)
    else IO.raiseError(new RuntimeException(s"Format $format not supported to serialize $name. Supported formats=$formats"))
  } yield str))

  override def empty: Schema = ShaclexSchema.empty

  override def shapes: List[String] = {
    schema.shapes.map(_.id).map(_.toString).toList
  }

  override def pm: PrefixMap = schema.pm

  override def convert(maybeTargetFormat: Option[String],
                       maybeTargetEngine: Option[String],
                       base: Option[IRI]
                      ): IO[String] = {
   val targetFormat = maybeTargetFormat.getOrElse(DataFormats.defaultFormatName)
   for {
    str <- maybeTargetEngine.map(_.toUpperCase) match {
     case None => 
       serialize(targetFormat)
     case Some("SHACL") | Some("SHACLEX") =>
       serialize(targetFormat)
     case Some("SHEX") => RDFAsJenaModel.empty.flatMap(_.use(builder => for {
       pair <- Shacl2ShEx.shacl2ShEx(schema).fold(
         s => IO.raiseError(new RuntimeException(s"SHACL2ShEx: Error converting: $s")),
         IO.pure
       )
       (newSchema,_) = pair
       str <- es.weso.shex.Schema.serialize(newSchema,targetFormat,base,builder)
     } yield (str)))
     case Some(other) =>
       IO.raiseError(new RuntimeException(s"Conversion $name -> $other not implemented yet"))
   } 
  } yield str
 }

  override def info: SchemaInfo = {
    // TODO: Check if shacl schemas are well formed
    SchemaInfo(name,"SHACLex", isWellFormed = true, List())
  }

  override def toClingo(rdf: RDFReader, shapeMap: ShapeMap): IO[String] =
    IO.raiseError(new RuntimeException(s"Not implemented yet"))

}

object ShaclexSchema {
  def empty: ShaclexSchema = ShaclexSchema(schema = ShaclSchema.empty)

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy