Greg Cox
Greg Cox

Reputation: 287

Why SHACL API's SHShape.hasTargetNode Returns False For Targeted Class?

In the course of trying to selectively invoke shapes/rules when classes are asserted, I'm working with the following example shapes definition (in TTL):

# baseURI: http://example.org/familyShapes
# imports: http://datashapes.org/dash
# prefix: familyShapes

@prefix dash: <http://datashapes.org/dash#> .
@prefix familyShapes: <http://example.org/familyShapes#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://example.org/familyShapes>
  rdf:type owl:Ontology ;
  owl:imports <http://datashapes.org/dash> ;
.
familyShapes:FemaleShape
  rdf:type sh:NodeShape ;
  rdfs:label "Female shape" ;
  sh:property [
      sh:path familyShapes:gender ;
      sh:hasValue familyShapes:female ;
    ] ;
.
familyShapes:Gender
  rdf:type rdfs:Class ;
  rdfs:label "Gender" ;
  rdfs:subClassOf rdfs:Resource ;
.
familyShapes:GrandpaRuleShape
  rdf:type sh:NodeShape ;
  rdfs:label "Grandpa rule shape" ;
  sh:rule [
      rdf:type sh:SPARQLRule ;
      rdfs:comment "Shape to infer grandpa/grandchild relationship" ;
      rdfs:label "Infer grandpas and grandchildren" ;
      sh:construct """
        PREFIX familyShapes: <http://example.org/familyShapes#>

        CONSTRUCT {
          ?child familyShapes:grandPa $this .
          $this familyShapes:grandChild ?child .
        }
        WHERE {
        {
           ?child familyShapes:mother ?mom .
           ?mom familyShapes:father $this .
        }
        UNION 
        {
           ?child familyShapes:father ?dad .
           ?dad familyShapes:father $this .
        }
      }
   """ ;
      sh:order 10 ;
    ] ;
  sh:targetClass familyShapes:Person ;
.
familyShapes:MaleShape
  rdf:type sh:NodeShape ;
  rdfs:label "Male shape" ;
  sh:property [
      sh:path familyShapes:gender ;
      sh:hasValue familyShapes:male ;
    ] ;
.
familyShapes:Person
  rdf:type rdfs:Class ;
  rdf:type sh:NodeShape ;
  rdfs:label "Person" ;
  rdfs:subClassOf rdfs:Resource ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path familyShapes:father ;
      sh:class familyShapes:Person ;
      sh:description "A Person's father." ;
      sh:maxCount 1 ;
      sh:name "father" ;
      sh:node familyShapes:MaleShape ;
      sh:nodeKind sh:IRI ;
      sh:sparql [
          sh:message "A person cannot be a father to that same person." ;
          sh:select """PREFIX familyShapes: <http://example.org/familyShapes#>

SELECT $this
WHERE {
    $this familyShapes:father $this .
}""" ;
        ] ;
    ] ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path familyShapes:firstName ;
      sh:datatype xsd:string ;
      sh:description "A Person's first name (aka given name)." ;
      sh:minCount 1 ;
      sh:name "first name" ;
    ] ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path familyShapes:gender ;
      sh:class familyShapes:Gender ;
      sh:description "A Person's gender." ;
      sh:maxCount 1 ;
      sh:minCount 1 ;
      sh:name "gender" ;
    ] ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path familyShapes:lastName ;
      sh:datatype xsd:string ;
      sh:description "A Person's last name (aka family name)." ;
      sh:maxCount 1 ;
      sh:minCount 1 ;
      sh:name "last name" ;
    ] ;
  sh:property [
      rdf:type sh:PropertyShape ;
      sh:path familyShapes:mother ;
      sh:class familyShapes:Person ;
      sh:description "A Person's mother." ;
      sh:maxCount 1 ;
      sh:name "mother" ;
      sh:node familyShapes:FemaleShape ;
      sh:nodeKind sh:IRI ;
      sh:sparql [
          rdfs:comment "A person cannot be that same person's mother." ;
          sh:message "A person cannot be that same person's mother." ;
          sh:select """PREFIX familyShapes: <http://example.org/familyShapes#>

SELECT $this 
WHERE {
    $this familyShapes:mother $this .
}""" ;
        ] ;
    ] ;
  sh:rule [
      rdf:type sh:SPARQLRule ;
      rdfs:label "Infer grandmas and grandchildren" ;
      sh:construct """PREFIX familyShapes: <http://example.org/familyShapes#>

CONSTRUCT {
      ?child familyShapes:grandMa $this .
      $this familyShapes:grandChild ?child .
}
WHERE {
      {
        ?child familyShapes:mother ?mom .
        ?mom familyShapes:mother $this .
      }
      UNION 
      {
        ?child familyShapes:father ?dad .
        ?dad familyShapes:mother $this .
      }
}
        """ ;
    ] ;
.
familyShapes:female
  rdf:type familyShapes:Gender ;
  rdfs:label "female" ;
.
familyShapes:firstName
  rdf:type rdf:Property ;
  rdfs:comment "A Person's first name (aka given name)." ;
  rdfs:label "first name" ;
.
familyShapes:grandChild
  rdf:type owl:ObjectProperty ;
  rdfs:domain familyShapes:Person ;
  rdfs:label "grand child" ;
  rdfs:range familyShapes:Person ;
.
familyShapes:grandMa
  rdf:type owl:ObjectProperty ;
  rdfs:domain familyShapes:Person ;
  rdfs:label "grand ma" ;
  rdfs:range familyShapes:Person ;
.
familyShapes:grandPa
  rdf:type owl:ObjectProperty ;
  rdfs:domain familyShapes:Person ;
  rdfs:label "grand pa" ;
  rdfs:range familyShapes:Person ;
.
familyShapes:male
  rdf:type familyShapes:Gender ;
  rdfs:label "male" ;
.
familyShapes:mother
  rdf:type rdf:Property ;
  rdfs:comment "A Person's mother." ;
  rdfs:label "mother" ;
.

I'm focused at this time on the familyShapes:GrandpaRuleShape shape (starting on line 30) which, I believe, on line 58 targets the familyShapes:Person class.

The SHACL API's RuleUtil.getShapesWithTargetNode method returns an empty list, which is not the result I was expecting, so I've created a temporary local copy of the RuleUtil.getShapesWithTargetNode method as shown below to help me debug my own code.

private static List<Shape> getShapesWithTargetNode(RDFNode focusNode, ShapesGraph shapesGraph) {
    // TODO: Not a particularly smart algorithm - walks all shapes that have rules
    List<Shape> shapes = new ArrayList<>();
    for(Shape shape : shapesGraph.getRootShapes()) {
        SHShape sr = shape.getShapeResource();
        boolean shapeHasRule = sr.hasProperty(SH.rule);
        boolean shapeFocused = sr.hasTargetNode(focusNode);
        if(shapeHasRule && shapeFocused) {
            shapes.add(shape);
        }
    }
    return shapes;
}

I've stopped execution in the debugger in this method with focusNode=http://example.org/familyShapes#Person and the shapesGraph representing the shapes file above. The breakpoint is at the conditional in the for loop, after the two booleans have been assigned. The first value of shape is familyShapes:GrandpaRuleShape. However, the boolean shapeFocused is false. The boolean shapeHasRule is true as expected.

I was expecting that shapeFocused would be true at this point of execution. At a higher level, I was expecting that this method would return a list at least containing the grandpa shape, but it returns empty. I think I must be setting up the call to this method incorrectly, but I'm not sure what I'm doing wrong. Any suggestions?

Upvotes: 1

Views: 156

Answers (1)

Holger Knublauch
Holger Knublauch

Reputation: 1421

I think it works correctly. Person is a class and the rule shape has the sh:targetClass Person. This means that the focus/target nodes are the instances of that class. If you invoke the function with a specific instance of Person then it should work.

Upvotes: 2

Related Questions