blob: 14199b41e4ca5b92830ba86bf5f61d3115fdcc37 [file] [log] [blame]
/*
* 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.CompactWriter;
import org.apache.jena.shacl.engine.ValidationContext;
import org.apache.jena.shacl.lib.G;
import org.apache.jena.shacl.parser.Constraint;
import org.apache.jena.shacl.parser.Shape;
import org.apache.jena.shacl.validation.ValidationProc;
import org.apache.jena.shacl.vocabulary.SHACL;
import org.apache.jena.sparql.path.Path;
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;
}
@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<Node> 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.
*/
// XXX Siblings be calculated at parse time?
Collection<Node> sibs = siblings(vCxt.getShapesGraph(), shape);
Set<Node> 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++;
}
if ( qMin >= 0 && qMin > x ) {
String msg = toString()+": Min = "+qMin+" but got "+x+" validations";
vCxt.reportEntry(msg, shape, focusNode, path, null,
new ReportConstraint(SHACL.QualifiedMinCountConstraintComponent));
}
if ( qMax >= 0 && qMax < x ) {
String msg = toString()+": Max = "+qMax+" but got "+x+" validations";
vCxt.reportEntry(msg, shape, focusNode, path, null,
new ReportConstraint(SHACL.QualifiedMaxCountConstraintComponent));
}
}
private boolean conformsSiblings(ValidationContext vCxt, Node v, Collection<Node> 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<Node> siblings(Graph shapesGraph, Shape thisShape) {
if ( ! qDisjoint )
return Collections.emptySet();
Node thisShapeNode = thisShape.getShapeNode();
Set<Node> sibs = new HashSet<>();
List<Node> parents = G.listPO(shapesGraph, SHACL.property, thisShapeNode);
parents.forEach(s->{
List<Node> sibs1 = G.listSP(shapesGraph, s, SHACL.property);
sibs.addAll(sibs1);
});
Set<Node> sibShapes = new HashSet<>();
sibs.forEach(s->{
List<Node> 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 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);
}
}