blob: 2eeb95909fdf9c89580d1d06a23103cb8d3fdafc [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.parser;
import static org.apache.jena.shacl.lib.ShLib.displayStr;
import java.util.*;
import java.util.stream.Collectors;
import org.apache.jena.atlas.lib.NotImplemented;
import org.apache.jena.graph.Graph;
import org.apache.jena.graph.Node;
import org.apache.jena.graph.Triple;
import org.apache.jena.rdf.model.impl.Util;
import org.apache.jena.riot.other.G;
import org.apache.jena.shacl.engine.SparqlConstraints;
import org.apache.jena.shacl.engine.constraint.*;
import org.apache.jena.shacl.lib.ShLib;
import org.apache.jena.shacl.sys.C;
import org.apache.jena.shacl.vocabulary.SHACL;
public class Constraints {
// Core Constraint Components
//
// 4.1 Value Type Constraint Components
// 4.1.1 sh:class
// 4.1.2 sh:datatype
// 4.1.3 sh:nodeKind
// 4.2 Cardinality Constraint Components
// 4.2.1 sh:minCount
// 4.2.2 sh:maxCount
// 4.3 Value Range Constraint Components
// 4.3.1 sh:minExclusive
// 4.3.2 sh:minInclusive
// 4.3.3 sh:maxExclusive
// 4.3.4 sh:maxInclusive
// 4.4 String-based Constraint Components
// 4.4.1 sh:minLength
// 4.4.2 sh:maxLength
// 4.4.3 sh:pattern
// 4.4.4 sh:languageIn
// 4.4.5 sh:uniqueLang
// 4.5 Property Pair Constraint Components
// 4.5.1 sh:equals
// 4.5.2 sh:disjoint
// 4.5.3 sh:lessThan
// 4.5.4 sh:lessThanOrEquals
// 4.6 Logical Constraint Components
// 4.6.1 sh:not
// 4.6.2 sh:and
// 4.6.3 sh:or
// 4.6.4 sh:xone
// 4.7 Shape-based Constraint Components
// 4.7.1 sh:node
// 4.7.2 sh:property
// 4.7.3 sh:qualifiedValueShape, sh:qualifiedMinCount, sh:qualifiedMaxCount
// 4.8 Other Constraint Components
// 4.8.1 sh:closed, sh:ignoredProperties
// 4.8.2 sh:hasValue
// 4.8.3 sh:in
// The constraints that need just a single triple.
static Map<Node, ConstraintMaker> dispatch = new HashMap<>();
static {
dispatch.put( SHACL.class_, (g, s, p, o) -> new ClassConstraint(o) );
dispatch.put( SHACL.datatype, (g, s, p, o) -> new DatatypeConstraint(o) );
dispatch.put( SHACL.nodeKind, (g, s, p, o) -> new NodeKindConstraint(o) );
dispatch.put( SHACL.minCount, (g, s, p, o) -> new MinCount(intValue(o)) );
dispatch.put( SHACL.maxCount, (g, s, p, o) -> new MaxCount(intValue(o)) );
dispatch.put( SHACL.minInclusive, (g, s, p, o) -> new ValueMinInclusiveConstraint(o) );
dispatch.put( SHACL.minExclusive, (g, s, p, o) -> new ValueMinExclusiveConstraint(o) );
dispatch.put( SHACL.maxInclusive, (g, s, p, o) -> new ValueMaxInclusiveConstraint(o) );
dispatch.put( SHACL.maxExclusive, (g, s, p, o) -> new ValueMaxExclusiveConstraint(o) );
dispatch.put( SHACL.minLength, (g, s, p, o) -> new StrMinLengthConstraint(intValue(o)) );
dispatch.put( SHACL.maxLength, (g, s, p, o) -> new StrMaxLengthConstraint(intValue(o)) );
// Below
//dispatch.put( SHACL.pattern, (g, p, o) -> notImplemented(p) );
dispatch.put( SHACL.languageIn, (g, s, p, o) -> new StrLanguageIn(listString(g, o)) );
dispatch.put( SHACL.uniqueLang, (g, s, p, o) -> new UniqueLangConstraint(booleanValueStrict(o)) );
dispatch.put( SHACL.hasValue, (g, s, p, o) -> new HasValueConstraint(o) );
dispatch.put( SHACL.in, (g, s, p, o) -> new InConstraint(list(g,o)) );
dispatch.put( SHACL.closed, (g, s, p, o) -> new ClosedConstraint(g,s,booleanValue(o)) );
dispatch.put( SHACL.equals, (g, s, p, o) -> new EqualsConstraint( checkObjectIRI(g, s, p, o)) );
dispatch.put( SHACL.disjoint, (g, s, p, o) -> new DisjointConstraint( checkObjectIRI(g, s, p, o)) );
dispatch.put( SHACL.lessThan, (g, s, p, o) -> new LessThanConstraint( checkObjectIRI(g, s, p, o)) );
dispatch.put( SHACL.lessThanOrEquals, (g, s, p, o) -> new LessThanOrEqualsConstraint( checkObjectIRI(g, s, p, o)) );
// Below
//dispatch.put( SHACL.not, (g, s, p, o) -> notImplemented(p) );
//dispatch.put( SHACL.and, (g, s, p, o) -> notImplemented(p) );
//dispatch.put( SHACL.or, (g, s, p, o) -> notImplemented(p) );
//dispatch.put( SHACL.xone, (g, s, p, o) -> notImplemented(p) );
//dispatch.put( SHACL.node, (g, s, p, o) -> notImplemented(p) );
dispatch.put(SHACL.sparql, (g, s, p, o) -> SparqlConstraints.parseSparqlConstraint(g, s, p, o) );
}
/**
* The constraints that just need an input node, and do not look in the data.
* For example, minCount is not here because needs all the instances to count them.
*/
static Set<Node> immediate = new HashSet<>();
static {
immediate.add(SHACL.datatype);
immediate.add(SHACL.nodeKind);
// Value
immediate.add(SHACL.minExclusive);
immediate.add(SHACL.minInclusive);
immediate.add(SHACL.maxExclusive);
immediate.add(SHACL.maxInclusive);
// String
immediate.add(SHACL.minLength);
immediate.add(SHACL.maxLength);
immediate.add(SHACL.languageIn);
//
immediate.add(SHACL.in);
immediate.add(SHACL.pattern);
}
/**
* Entry point. Process all triples of a specific shape node (subject). Has
* access to map of parsed shapes so it can recursively call back into the shapes
* parser at when the constraint uses other shapes
* (sh:and/sh:or/sh:not/sh:xone.sh:node).
*/
/*package*/ static List<Constraint> parseConstraints(Graph shapesGraph, Node shape, Map<Node, Shape> parsed, Set<Node> traversed) {
List<Constraint> constraints = new ArrayList<>();
Iterator<Triple> iter = G.find(shapesGraph, shape, null, null);
iter.forEachRemaining(t -> {
Node p = t.getPredicate();
// The parser handles sh:property specially as a PropertyShape.
if ( SHACL.property.equals(p) )
return;
if ( SHACL.path.equals(p) )
return;
Node s = t.getSubject();
Node o = t.getObject();
Constraint c = parseConstraint(shapesGraph, s, p, o, parsed, traversed);
if ( c != null )
constraints.add(c);
});
return constraints;
}
/**
* The translate of an RDF triple into a {@link Constraint}.
* Constraints require more that just the triple being inspected.
*/
private static Constraint parseConstraint(Graph g, Node s, Node p, Node o, Map<Node, Shape> parsed, Set<Node> traversed) {
// Test for single triple constraints.
ConstraintMaker maker = dispatch.get(p);
if ( maker != null )
return maker.make(g, s, p, o);
// These require the "parsed" map.
if ( p.equals(SHACL.not) ) {
Shape shape = ShapesParser.parseShapeStep(traversed, parsed, g, o);
return new ShNot(shape);
}
if ( p.equals(SHACL.or) ) {
List<Node> elts = list(g, o);
List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(traversed, parsed, g, x)).collect(Collectors.toList());
return new ShOr(shapes);
}
if ( p.equals(SHACL.and) ) {
List<Node> elts = list(g, o);
List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(traversed, parsed, g, x)).collect(Collectors.toList());
return new ShAnd(shapes);
}
if ( p.equals(SHACL.xone) ) {
List<Node> elts = list(g, o);
List<Shape> shapes = elts.stream().map(x->ShapesParser.parseShapeStep(traversed, parsed, g, x)).collect(Collectors.toList());
return new ShXone(shapes);
}
if ( p.equals(SHACL.node) ) {
Shape other = ShapesParser.parseShapeStep(traversed, parsed, g, o);
if ( other instanceof PropertyShape )
throw new ShaclParseException("Object of sh:node must be a node shape, not a property shape");
return new ShNode(other);
}
// sh:pattern is influenced by an adjacent sh:flags.
if ( p.equals(SHACL.pattern) ) {
Node pat = o;
if ( ! Util.isSimpleString(pat) )
throw new ShaclParseException("Pattern is not a string: Node = "+displayStr(s)+" : Pattern = "+displayStr(pat) );
Node flagsNode = G.getSP(g, s, SHACL.flags);
if ( flagsNode != null && ! Util.isSimpleString(flagsNode) )
throw new ShaclParseException("Pattern flags not a string: Node = "+displayStr(s)+" : Pattern = "+displayStr(flagsNode) );
return new PatternConstraint(pat.getLiteralLexicalForm(), (flagsNode!=null)?flagsNode.getLiteralLexicalForm(): null);
}
// Known component parameters.
if ( p.equals(SHACL.ignoredProperties) )
return null;
if ( p.equals(SHACL.qualifiedValueShape) )
return parseQualifiedValueShape(g, s, p, o, parsed, traversed);
// sh:qualifiedValueShape parameters.
if ( p.equals(SHACL.QualifiedMinCountConstraintComponent) ||
p.equals(SHACL.QualifiedMaxCountConstraintComponent) ||
p.equals(SHACL.qualifiedValueShapesDisjoint) )
return null;
// Non-Validating Property Shape Characteristics - in ShapesParser.parseShape$
// if ( p.equals(SHACL.group) ) return null;
// if ( p.equals(SHACL.name) ) return null;
// if ( p.equals(SHACL.description) ) return null;
// if ( p.equals(SHACL.defaultValue) ) return null;
// if ( p.equals(SHACL.order) ) return null;
if ( p.equals(SHACL.path ) )
throw new ShaclParseException("Unexpected constraint: "+displayStr(p)+" on "+s);
if ( p.equals(SHACL.property) )
throw new ShaclParseException("Unexpected constraint: "+displayStr(p)+" on "+s);
return null;
}
private static Constraint parseQualifiedValueShape(Graph g, Node s, Node p, Node o, Map<Node, Shape> parsed, Set<Node> traversed) {
Shape sub = ShapesParser.parseShapeStep(traversed, parsed, g, o);
// [PARSE] Syntax check needed
Node qMin = G.getZeroOrOneSP(g, s, SHACL.qualifiedMinCount);
Node qMax = G.getZeroOrOneSP(g, s, SHACL.qualifiedMaxCount);
int vMin = intValue(qMin, -1);
int vMax = intValue(qMax, -1);
Node qDisjoint = G.getZeroOrOneSP(g, s, SHACL.qualifiedValueShapesDisjoint);
if ( vMin < 0 && vMax < 0 )
throw new ShaclParseException("At least one of sh:qualifiedMinCount and sh:qualifiedMaxCount required");
return new QualifiedValueShape(sub, intValue(qMin, -1), intValue(qMax, -1), booleanValueStrict(qDisjoint)) ;
}
interface ConstraintMaker {
Constraint make(Graph g, Node s, Node p, Node o);
}
private static Constraint notImplemented(Node p) {
throw new NotImplemented(ShLib.displayStr(p));
}
static Node checkObjectIRI(Graph g, Node shape, Node p, Node o) {
if ( ! o.isURI() )
throw new ShaclParseException("IRI required: "+displayStr(o) + " at "+shape+" "+displayStr(p));
return o;
}
static int intValue(Node n) {
return ((Integer)(n.getLiteralValue())).intValue();
}
static int intValue(Node n, int dftValue) {
if ( n == null )
return dftValue;
return intValue(n);
}
private static boolean booleanValueStrict(Node node) {
// Only "true"^^xsd:boolean is in the spec.
return C.TRUE.equals(node);
}
private static boolean booleanValue(Node node) {
return (Boolean)node.getLiteralValue();
}
/** Return the list elements of an RDF list start at {@code node} */
private static List<Node> list(Graph g, Node node) {
return G.rdfList(g, node);
}
private static List<String> listString(Graph g, Node node) {
List<Node> elts = list(g, node);
return elts.stream().map(n -> {
if ( ! Util.isSimpleString(n) )
throw new ShaclParseException("Not a string "+displayStr(n)+" in list "+elts);
return n.getLiteralLexicalForm();
}).collect(Collectors.toList());
}
}