org.apache.jena.shacl.engine.constraint.QualifiedValueShape Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jena-shacl Show documentation
Show all versions of jena-shacl Show documentation
SHACL engine for Apache Jena
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jena.shacl.engine.constraint;
import java.util.*;
import org.apache.jena.atlas.io.IndentedWriter;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.riot.out.NodeFormatter;
import org.apache.jena.shacl.ShaclException;
import org.apache.jena.shacl.ValidationReport;
import org.apache.jena.shacl.compact.writer.CompactOut;
import org.apache.jena.shacl.compact.writer.CompactWriter;
import org.apache.jena.shacl.engine.ValidationContext;
import org.apache.jena.shacl.parser.Constraint;
import org.apache.jena.shacl.parser.ConstraintVisitor;
import org.apache.jena.shacl.parser.Shape;
import org.apache.jena.shacl.validation.ValidationProc;
import org.apache.jena.shacl.validation.event.ConstraintEvaluatedOnPathNodesWithCompareNodesEvent;
import org.apache.jena.shacl.vocabulary.SHACL;
import org.apache.jena.sparql.path.Path;
import org.apache.jena.system.G;
public class QualifiedValueShape implements Constraint {
private final Shape sub;
private int qMin;
private int qMax;
private boolean qDisjoint;
public QualifiedValueShape(Shape sub, int qMin, int qMax, boolean qDisjoint) {
this.sub = sub;
this.qMin = qMin;
this.qMax = qMax;
this.qDisjoint = qDisjoint;
}
public Shape getSub() {
return sub;
}
public int qMin() {
return qMin;
}
public int qMax() {
return qMax;
}
public boolean qDisjoint() {
return qDisjoint;
}
@Override
public void validateNodeShape(ValidationContext vCxt, Graph data, Shape shape, Node focusNode) {
throw new ShaclException("sh:qualifiedValueShape only valid in a property shape");
}
@Override
public void validatePropertyShape(ValidationContext vCxt, Graph data, Shape shape, Node focusNode, Path path, Set valueNodes) {
/*
* Let Q be a shape in shapes graph G that declares a qualified cardinality
* constraint (by having values for sh:qualifiedValueShape and at least one of
* sh:qualifiedMinCount or sh:qualifiedMaxCount). Let ps be the set of shapes in G
* that have Q as a value of sh:property.
*
* If Q has true as a value for
* sh:qualifiedValueShapesDisjoint then the set of sibling shapes for Q is defined as
* the set of all values of the SPARQL property path
* sh:property/sh:qualifiedValueShape for any shape in ps minus the value of
* sh:qualifiedValueShape of Q itself.
*
* The set of sibling shapes is empty
* otherwise.
*/
/* TEXTUAL DEFINITION of sh:qualifiedMinCount
*
* Let C be the number of value nodes v where v conforms to $qualifiedValueShape and
* where v does not conform to any of the sibling shapes for the current shape, i.e.
* the shape that v is validated against and which has $qualifiedValueShape as its
* value for sh:qualifiedValueShape.
*
* A failure MUST be produced if any of the said conformance checks produces a failure.
* Otherwise, there is a validation result if C is less than $qualifiedMinCount.
*
* The constraint component for sh:qualifiedMinCount is sh:QualifiedMinCountConstraintComponent.
*/
Collection sibs = siblings(vCxt.getShapesGraph(), shape);
Set valueNodes2;
if ( qDisjoint ) {
valueNodes2 = new HashSet<>();
for ( Node v : valueNodes ) {
// Ignore disjoint on siblings?
if ( ! conformsSiblings(vCxt, v, sibs) ) {
// No sibling => candidate.
valueNodes2.add(v);
}
}
} else {
valueNodes2 = valueNodes;
}
int x = 0;
for ( Node v : valueNodes2 ) {
boolean b = conforms(vCxt, sub, v);
if ( b )
x++;
}
boolean passed = true;
if ( qMin >= 0 && qMin > x ) {
passed = false;
String msg = toString()+": Min = "+qMin+" but got "+x+" validations";
vCxt.reportEntry(msg, shape, focusNode, path, null,
new ReportConstraint(SHACL.QualifiedMinCountConstraintComponent));
}
final int finalX = x;
boolean finalPassed = passed;
if (qMin > 0) {
vCxt.notifyValidationListener(() -> new QualifiedMinCountConstraintEvaluatedEvent(vCxt, shape,
focusNode, this, path, valueNodes, valueNodes2, qMin, finalX,
finalPassed));
}
passed = true;
if ( qMax >= 0 && qMax < x ) {
passed = false;
String msg = toString()+": Max = "+qMax+" but got "+x+" validations";
vCxt.reportEntry(msg, shape, focusNode, path, null,
new ReportConstraint(SHACL.QualifiedMaxCountConstraintComponent));
}
if (qMax > 0) {
boolean finalPassed2 = passed;
vCxt.notifyValidationListener(() -> new QualifiedMaxCountConstraintEvaluatedEvent(vCxt, shape,
focusNode, this, path, valueNodes, valueNodes2, qMax, finalX,
finalPassed2));
}
}
private boolean conformsSiblings(ValidationContext vCxt, Node v, Collection sibs) {
for ( Node sib : sibs ) {
Shape sibShape = vCxt.getShapes().getShape(sib);
boolean b = conforms(vCxt, sibShape, v);
if ( b )
return true;
}
return false;
}
private static boolean conforms(ValidationContext vCxt, Shape shape, Node v) {
ValidationContext vCxt2 = ValidationContext.create(vCxt);
ValidationProc.execValidateShape(vCxt2, vCxt.getDataGraph(), shape, v);
ValidationReport report = vCxt2.generateReport();
return report.conforms();
}
private Collection siblings(Graph shapesGraph, Shape thisShape) {
if ( ! qDisjoint )
return Collections.emptySet();
Node thisShapeNode = thisShape.getShapeNode();
Set sibs = new HashSet<>();
List parents = G.listPO(shapesGraph, SHACL.property, thisShapeNode);
parents.forEach(s->{
List sibs1 = G.listSP(shapesGraph, s, SHACL.property);
sibs.addAll(sibs1);
});
Set sibShapes = new HashSet<>();
sibs.forEach(s->{
List x = G.listSP(shapesGraph, s, SHACL.qualifiedValueShape);
sibShapes.addAll(x);
});
sibShapes.remove(sub.getShapeNode());
return sibShapes;
}
@Override
public Node getComponent() {
return SHACL.qualifiedValueShape;
}
@Override
public void visit(ConstraintVisitor visitor){
visitor.visit(this);
}
@Override
public void printCompact(IndentedWriter out, NodeFormatter nodeFmt) {
// 'qualifiedValueShape' | 'qualifiedMinCount' | 'qualifiedMaxCount' | 'qualifiedValueShapesDisjoint'
boolean outputDone = false;
if ( qMin >= 0 ) {
CompactOut.compact(out, "qualifiedMinCount", qMin);
outputDone = true;
}
if ( qMax >= 0 ) {
if ( outputDone )
out.print(" ");
CompactOut.compact(out, "qualifiedMaxCount", qMax);
outputDone = true;
}
if ( qDisjoint ) {
if ( outputDone )
out.print(" ");
CompactOut.compactUnquotedString(out, "qualifiedValueShapesDisjoint", "true");
outputDone = true;
}
if ( outputDone )
out.print(" ");
CompactWriter.output(out, nodeFmt, sub);
}
@Override
public String toString() {
return String.format("QualifiedValueShape[%s,%s,%s]",
(qMin<0) ? "_" : Integer.toString(qMin),
(qMax<0) ? "_" : Integer.toString(qMax),
qDisjoint);
}
public static class QualifiedMinCountConstraintEvaluatedEvent extends
ConstraintEvaluatedOnPathNodesWithCompareNodesEvent {
final int minCount;
final int actualCount;
public QualifiedMinCountConstraintEvaluatedEvent(ValidationContext vCxt, Shape shape,
Node focusNode, Constraint constraint, Path path, Set valueNodes,
Set compareNodes, int minCount, int actualCount, boolean valid) {
super(vCxt, shape, focusNode, constraint, path, valueNodes, compareNodes, valid);
this.minCount = minCount;
this.actualCount = actualCount;
}
public int getMinCount() {
return minCount;
}
public int getActualCount() {
return actualCount;
}
}
public static class QualifiedMaxCountConstraintEvaluatedEvent extends
ConstraintEvaluatedOnPathNodesWithCompareNodesEvent {
final int maxCount;
final int actualCount;
public QualifiedMaxCountConstraintEvaluatedEvent(ValidationContext vCxt, Shape shape,
Node focusNode, Constraint constraint, Path path, Set valueNodes,
Set compareNodes, int maxCount, int actualCount, boolean valid) {
super(vCxt, shape, focusNode, constraint, path, valueNodes, compareNodes, valid);
this.maxCount = maxCount;
this.actualCount = actualCount;
}
public int getMaxCount() {
return maxCount;
}
public int getActualCount() {
return actualCount;
}
}
}