Skip to content

Commit

Permalink
Improve error logging when a resource can't be parsed back in composi…
Browse files Browse the repository at this point in the history
…te views (#4849)

Co-authored-by: Simon Dumas <[email protected]>
  • Loading branch information
imsdu and Simon Dumas authored Apr 8, 2024
1 parent 2d9ecb9 commit 5ab5954
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.BlazegraphClient
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlQueryResponseType.SparqlNTriples
import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewProjection.idTemplating
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.graph.{Graph, NTriples}
import ch.epfl.bluebrain.nexus.delta.rdf.graph.Graph
import ch.epfl.bluebrain.nexus.delta.rdf.query.SparqlQuery.SparqlConstructQuery
import fs2.Chunk

Expand All @@ -26,14 +26,10 @@ final class BatchQueryGraph(client: BlazegraphClient, namespace: String, query:

private val logger = Logger[BatchQueryGraph]

private def newGraph(ntriples: NTriples): IO[Option[Graph]] =
if (ntriples.isEmpty) IO.none
else IO.fromEither(Graph(ntriples)).map(Some(_))

def apply(ids: Chunk[Iri]): IO[Option[Graph]] =
for {
ntriples <- client.query(Set(namespace), replaceIds(query, ids), SparqlNTriples)
graphResult <- newGraph(ntriples.value)
graphResult <- NTripleParser(ntriples.value, None)
_ <- IO.whenA(graphResult.isEmpty)(
logger.debug(s"Querying blazegraph did not return any triples, '$ids' will be dropped.")
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.indexing

import cats.effect.IO
import ch.epfl.bluebrain.nexus.delta.kernel.Logger
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.ParsingError
import ch.epfl.bluebrain.nexus.delta.rdf.graph.{Graph, NTriples}

object NTripleParser {

private val logger = Logger[NTripleParser.type]

def apply(ntriples: NTriples, rootNodeOpt: Option[Iri]): IO[Option[Graph]] =
if (ntriples.isEmpty) {
// If nothing is returned by the query, we skip
IO.none
} else {
rootNodeOpt match {
case Some(rootNode) =>
IO.fromEither(Graph(ntriples.copy(rootNode = rootNode))).map { g =>
Some(g.replaceRootNode(rootNode))
}
case None =>
IO.fromEither(Graph(ntriples)).map(Some(_))
}
}.onError {
case p: ParsingError =>
logger.error(p)("Blazegraph did not send back valid n-triples, please check the responses")
case _ => IO.unit
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.BlazegraphClient
import ch.epfl.bluebrain.nexus.delta.plugins.blazegraph.client.SparqlQueryResponseType.SparqlNTriples
import ch.epfl.bluebrain.nexus.delta.plugins.compositeviews.model.CompositeViewProjection.idTemplating
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.graph.{Graph, NTriples}
import ch.epfl.bluebrain.nexus.delta.rdf.query.SparqlQuery.SparqlConstructQuery
import ch.epfl.bluebrain.nexus.delta.sourcing.state.GraphResource

Expand All @@ -26,19 +25,10 @@ final class SingleQueryGraph(client: BlazegraphClient, namespace: String, query:

private val logger = Logger[SingleQueryGraph]

private def newGraph(ntriples: NTriples, id: Iri): IO[Option[Graph]] =
if (ntriples.isEmpty) {
// If nothing is returned by the query, we skip
IO.none
} else
IO.fromEither(Graph(ntriples.copy(rootNode = id))).map { g =>
Some(g.replaceRootNode(id))
}

def apply(graphResource: GraphResource): IO[Option[GraphResource]] =
for {
ntriples <- client.query(Set(namespace), replaceId(query, graphResource.id), SparqlNTriples)
graphResult <- newGraph(ntriples.value, graphResource.id)
graphResult <- NTripleParser(ntriples.value, Some(graphResource.id))
_ <- IO.whenA(graphResult.isEmpty)(
logger.debug(s"Querying blazegraph did not return any triples, '$graphResource' will be dropped.")
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package ch.epfl.bluebrain.nexus.delta.rdf

import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.Iri
import ch.epfl.bluebrain.nexus.delta.rdf.graph.{NQuads, NTriples}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolutionError
import ch.epfl.bluebrain.nexus.delta.rdf.query.SparqlQuery.SparqlConstructQuery
import io.circe.Encoder
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredEncoder
import org.apache.jena.riot.Lang

import scala.annotation.nowarn

Expand Down Expand Up @@ -41,6 +43,32 @@ object RdfError {
final case class ConversionError(details: String, stage: String)
extends RdfError(s"Error on the conversion stage '$stage'", Some(details))

final case class ParsingError(lang: String, message: String, rootNode: IriOrBNode, headValue: String)
extends RdfError(
s"Error while parsing $lang for id $rootNode: '$message'",
Some(s"Value:\n$headValue...")
)

object ParsingError {

private val limit = 500
def apply(message: String, nTriples: NTriples): ParsingError =
ParsingError(
Lang.NTRIPLES.getName,
message,
nTriples.rootNode,
nTriples.value.substring(0, limit)
)

def apply(message: String, nQuads: NQuads): ParsingError =
ParsingError(
Lang.NQUADS.getName,
message,
nQuads.rootNode,
nQuads.value.substring(0, limit)
)
}

/**
* Missing required predicate.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import cats.effect.IO
import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.{BNode, Iri}
import ch.epfl.bluebrain.nexus.delta.rdf.Quad.Quad
import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.{ConversionError, SparqlConstructQueryError, UnexpectedJsonLd}
import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.{ParsingError, SparqlConstructQueryError, UnexpectedJsonLd}
import ch.epfl.bluebrain.nexus.delta.rdf.Triple.{obj, predicate, subject, Triple}
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.rdf
import ch.epfl.bluebrain.nexus.delta.rdf._
Expand All @@ -24,10 +24,9 @@ import org.apache.jena.query.{DatasetFactory, QueryExecutionFactory}
import org.apache.jena.riot.{Lang, RDFParser, RDFWriter}
import org.apache.jena.sparql.core.DatasetGraph
import org.apache.jena.sparql.graph.GraphFactory
import org.apache.jena.sparql.util.IsoMatcher

import java.util.UUID
import scala.annotation.{nowarn, tailrec}
import scala.annotation.tailrec
import scala.jdk.CollectionConverters._
import scala.util.Try

Expand All @@ -39,7 +38,6 @@ import scala.util.Try
* @param value
* the Jena dataset graph
*/
@nowarn("cat=deprecation")
final case class Graph private (rootNode: IriOrBNode, value: DatasetGraph) { self =>

val rootResource: Node = subject(rootNode)
Expand Down Expand Up @@ -75,19 +73,6 @@ final case class Graph private (rootNode: IriOrBNode, value: DatasetGraph) { sel
copy(value = newGraph)
}

/**
* Compare this graph with another using the method described in <a
* href="https://www.w3.org/TR/rdf-concepts/#graph-isomorphism">
* https://www.w3.org/TR/rdf-concepts/#graph-isomorphism </a>
*
* @param g
* The graph to compare to
* @return
* boolean True if the two graphs are isomorphic.
*/
def isIsomorphic(other: Graph) =
IsoMatcher.isomorphic(value, other.value)

/**
* Returns a triple matching the predicate if found.
*/
Expand Down Expand Up @@ -184,7 +169,7 @@ final case class Graph private (rootNode: IriOrBNode, value: DatasetGraph) { sel
*/
def add(triple: Set[Triple]): Graph = {
val newGraph = copyGraph(value)
triple.foreach { case (s, p, o) => newGraph.getDefaultGraph.add(new JenaTriple(s, p, o)) }
triple.foreach { case (s, p, o) => newGraph.getDefaultGraph.add(JenaTriple.create(s, p, o)) }
copy(value = newGraph)
}

Expand Down Expand Up @@ -366,20 +351,20 @@ object Graph {
/**
* Generates a [[Graph]] from the passed ''nTriples'' representation
*/
final def apply(nTriples: NTriples): Either[ConversionError, Graph] = {
final def apply(nTriples: NTriples): Either[ParsingError, Graph] = {
val g = DatasetFactory.create().asDatasetGraph()
Try(RDFParser.create().fromString(nTriples.value).lang(Lang.NTRIPLES).parse(g)).toEither
.leftMap(err => ConversionError(err.getMessage, "NTriples to Graph"))
.leftMap(err => ParsingError(err.getMessage, nTriples))
.as(Graph(nTriples.rootNode, g))
}

/**
* Generates a [[Graph]] from the passed ''nQuads'' representation
*/
final def apply(nQuads: NQuads): Either[ConversionError, Graph] = {
final def apply(nQuads: NQuads): Either[ParsingError, Graph] = {
val g = DatasetFactory.create().asDatasetGraph()
Try(RDFParser.create().fromString(nQuads.value).lang(Lang.NQUADS).parse(g)).toEither
.leftMap(err => ConversionError(err.getMessage, "NQuads to Graph"))
.leftMap(err => ParsingError(err.getMessage, nQuads))
.as(Graph(nQuads.rootNode, g))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.GraphHelpers
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.BNode
import ch.epfl.bluebrain.nexus.delta.rdf.RdfError.{ConversionError, UnexpectedJsonLd}
import ch.epfl.bluebrain.nexus.delta.rdf.Triple._
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.{nxv, schema}
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.schema
import ch.epfl.bluebrain.nexus.delta.rdf.graph.Graph.rdfType
import ch.epfl.bluebrain.nexus.delta.rdf.implicits._
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdJavaApi, JsonLdOptions}
Expand Down Expand Up @@ -276,54 +276,5 @@ class GraphSpec extends CatsEffectSpec with GraphHelpers with CirceLiteral {
val expanded = ExpandedJsonLd.expanded(expandedJson).rightValue
Graph(expanded)(JsonLdJavaApi.lenient, JsonLdOptions.defaults).rightValue
}

"be isomorphic from the same expanded json value" in {
val graph2 = expanded.toGraph.rightValue
graph.isIsomorphic(graph2) shouldEqual true
}

"not be isomorphic when there is another type" in {
val graph2 = expanded.addType(nxv + "ExtraType").toGraph.rightValue
graph.isIsomorphic(graph2) shouldEqual false
}

"not be isomorphic when a property is missing" in {
val graph2 = expanded.remove(schema + "deprecated").toGraph.rightValue
graph.isIsomorphic(graph2) shouldEqual false
}

"not be isomorphic when a literal is different" in {
val graph2 = expanded.add(schema + "deprecated", value = true).toGraph.rightValue
graph.isIsomorphic(graph2) shouldEqual false
}

def source(value1: Int, value2: Int) =
json"""
[
{
"@id": "https://bbp.epfl.ch/array",
"@type": [ "http://schema.org/Array" ],
"https://bbp.epfl.ch/value": [
{
"https://bbp.epfl.ch/number": [
{
"@value": $value1
},
{
"@value": $value2
}
]
}
]
}
]
"""

"be isomorphic when the same values in a array do not appear in the same order" in {
val graph1 = ExpandedJsonLd(source(1, 2)).accepted.toGraph.rightValue
val graph2 = ExpandedJsonLd(source(2, 1)).accepted.toGraph.rightValue

graph1.isIsomorphic(graph2) shouldEqual true
}
}
}

0 comments on commit 5ab5954

Please sign in to comment.