diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/LanguageInPropertyShape.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/LanguageInPropertyShape.java new file mode 100644 index 000000000..1fdb14871 --- /dev/null +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/LanguageInPropertyShape.java @@ -0,0 +1,70 @@ +/******************************************************************************* + * Copyright (c) 2018 Eclipse RDF4J contributors. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ +package org.eclipse.rdf4j.sail.shacl.AST; + + +import org.eclipse.rdf4j.common.iteration.Iterations; +import org.eclipse.rdf4j.model.Resource; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.vocabulary.SHACL; +import org.eclipse.rdf4j.repository.Repository; +import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; +import org.eclipse.rdf4j.sail.shacl.ShaclSailConnection; +import org.eclipse.rdf4j.sail.shacl.planNodes.LanguageInFilter; +import org.eclipse.rdf4j.sail.shacl.planNodes.PlanNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Håvard Ottestad + */ +public class LanguageInPropertyShape extends PathPropertyShape { + + private final List languageIn; + private static final Logger logger = LoggerFactory.getLogger(LanguageInPropertyShape.class); + + LanguageInPropertyShape(Resource id, SailRepositoryConnection connection, NodeShape nodeShape) { + super(id, connection, nodeShape); + + try (Stream stream = Iterations.stream(connection.getStatements(id, SHACL.LANGUAGE_IN, null, true))) { + Resource orList = stream.map(Statement::getObject).map(v -> (Resource) v).findAny().orElseThrow(() -> new RuntimeException("Expected to find sh:languageIn on " + id)); + languageIn = toList(connection, orList).stream().map(Value::stringValue).collect(Collectors.toList()); + } + + } + + + @Override + public PlanNode getPlan(ShaclSailConnection shaclSailConnection, NodeShape nodeShape, boolean printPlans, boolean assumeBaseSailValid) { + + PlanNode invalidValues = StandardisedPlanHelper.getGenericSingleObjectPlan( + shaclSailConnection, + nodeShape, + (parent, trueNode, falseNode) -> new LanguageInFilter(parent, trueNode, falseNode, languageIn), + this + ); + + if (printPlans) { + String planAsGraphvizDot = getPlanAsGraphvizDot(invalidValues, shaclSailConnection); + logger.info(planAsGraphvizDot); + } + + return invalidValues; + + } + + @Override + public boolean requiresEvaluation(Repository addedStatements, Repository removedStatements) { + return true; + } +} diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/OrPropertyShape.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/OrPropertyShape.java index 642fbe2f6..c5cb2ae20 100644 --- a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/OrPropertyShape.java +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/OrPropertyShape.java @@ -50,26 +50,8 @@ public class OrPropertyShape extends PropertyShape { } - private static List toList(SailRepositoryConnection connection, Resource orList) { - List ret = new ArrayList<>(); - while (!orList.equals(RDF.NIL)) { - try (Stream stream = Iterations.stream(connection.getStatements(orList, RDF.FIRST, null))) { - Value value = stream.map(Statement::getObject).findAny().get(); - ret.add(value); - } - - try (Stream stream = Iterations.stream(connection.getStatements(orList, RDF.REST, null))) { - orList = stream.map(Statement::getObject).map(v -> (Resource) v).findAny().get(); - } - - } - return ret; - - - } - @Override public PlanNode getPlan(ShaclSailConnection shaclSailConnection, NodeShape nodeShape, boolean printPlans, boolean assumeBaseSailValid) { diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/PropertyShape.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/PropertyShape.java index b48ba8f18..9bc8fce36 100644 --- a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/PropertyShape.java +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/AST/PropertyShape.java @@ -11,6 +11,8 @@ import org.eclipse.rdf4j.common.iteration.Iterations; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.vocabulary.RDF; import org.eclipse.rdf4j.model.vocabulary.SHACL; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; @@ -82,6 +84,26 @@ public String getPlanAsGraphvizDot(PlanNode planNode, ShaclSailConnection shaclS } + static List toList(SailRepositoryConnection connection, Resource orList) { + List ret = new ArrayList<>(); + while (!orList.equals(RDF.NIL)) { + try (Stream stream = Iterations.stream(connection.getStatements(orList, RDF.FIRST, null))) { + Value value = stream.map(Statement::getObject).findAny().get(); + ret.add(value); + } + + try (Stream stream = Iterations.stream(connection.getStatements(orList, RDF.REST, null))) { + orList = stream.map(Statement::getObject).map(v -> (Resource) v).findAny().get(); + } + + } + + + return ret; + + + } + static class Factory { static List getPropertyShapes(Resource ShapeId, SailRepositoryConnection connection, NodeShape nodeShape) { @@ -131,6 +153,10 @@ static List getPropertyShapesInner(SailRepositoryConnection conne if (hasPattern(propertyShapeId, connection)) { propertyShapes.add(new PatternPropertyShape(propertyShapeId, connection, nodeShape)); } + + if (hasLanguageIn(propertyShapeId, connection)) { + propertyShapes.add(new LanguageInPropertyShape(propertyShapeId, connection, nodeShape)); + } return propertyShapes; } @@ -162,6 +188,9 @@ private static boolean hasMaxLength(Resource id, SailRepositoryConnection connec private static boolean hasPattern(Resource id, SailRepositoryConnection connection) { return connection.hasStatement(id, SHACL.PATTERN, null, true); } + private static boolean hasLanguageIn(Resource id, SailRepositoryConnection connection) { + return connection.hasStatement(id, SHACL.LANGUAGE_IN, null, true); + } } diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/planNodes/LanguageInFilter.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/planNodes/LanguageInFilter.java new file mode 100644 index 000000000..41d8e6ab2 --- /dev/null +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/planNodes/LanguageInFilter.java @@ -0,0 +1,49 @@ +/******************************************************************************* + * Copyright (c) 2018 Eclipse RDF4J contributors. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Distribution License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + *******************************************************************************/ + +package org.eclipse.rdf4j.sail.shacl.planNodes; + + +import org.eclipse.rdf4j.model.Literal; +import org.eclipse.rdf4j.model.Resource; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * @author Håvard Ottestad + */ +public class LanguageInFilter extends FilterPlanNode { + + private final List languageIn; + + public LanguageInFilter(PlanNode parent, PushBasedPlanNode trueNode, PushBasedPlanNode falseNode, List languageIn) { + super(parent, trueNode, falseNode); + this.languageIn = languageIn; + } + + @Override + boolean checkTuple(Tuple t) { + if(! (t.line.get(1) instanceof Literal)) return false; + + Optional language = ((Literal) t.line.get(1)).getLanguage(); + if(!language.isPresent()) return false; + + return languageIn.contains(language.get()); + + } + + + @Override + public String toString() { + return "LanguageInFilter{" + + "languageIn=" + Arrays.toString(languageIn.toArray()) + + '}'; + } +} diff --git a/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java b/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java index 128416502..1076e7d29 100644 --- a/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java +++ b/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ShaclTest.java @@ -41,6 +41,7 @@ public class ShaclTest { "test-cases/minLength/simple", "test-cases/maxLength/simple", "test-cases/pattern/simple", + "test-cases/languageIn/simple", "test-cases/minCount/simple", "test-cases/maxCount/simple", "test-cases/or/inheritance", diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case1/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case1/query1.rq new file mode 100644 index 000000000..9618efb03 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case1/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 a ex:Person ; + ex:label "människa"@se. + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case2/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case2/query1.rq new file mode 100644 index 000000000..5da6c03f7 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case2/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 a ex:Person ; + ex:label "human"@en. + + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case2/query2.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case2/query2.rq new file mode 100644 index 000000000..e17a1efc9 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case2/query2.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 ex:label "människa"@se. + + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case3/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case3/query1.rq new file mode 100644 index 000000000..e17a1efc9 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 ex:label "människa"@se. + + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case3/query2.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case3/query2.rq new file mode 100644 index 000000000..93fb72304 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case3/query2.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 a ex:Person . + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case4/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case4/query1.rq new file mode 100644 index 000000000..e17a1efc9 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case4/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 ex:label "människa"@se. + + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case4/query2.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case4/query2.rq new file mode 100644 index 000000000..5dbd7fecf --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case4/query2.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 + ex:label "human"@en ; + a ex:Person . + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case5/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case5/query1.rq new file mode 100644 index 000000000..c6956fd06 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case5/query1.rq @@ -0,0 +1,12 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 ex:label "människa"@se. + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case5/query2.rq b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case5/query2.rq new file mode 100644 index 000000000..9de958708 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/invalid/case5/query2.rq @@ -0,0 +1,18 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 + ex:label "human"@en ; + a ex:Person . + +ex:validPerson2 + ex:label "human"@en ; + a ex:Person . + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/shacl.ttl b/shacl/src/test/resources/test-cases/languageIn/simple/shacl.ttl new file mode 100644 index 000000000..0e641f022 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/shacl.ttl @@ -0,0 +1,16 @@ +@base . +@prefix ex: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix sh: . +@prefix xsd: . + +ex:PersonShape + a sh:NodeShape ; + sh:targetClass ex:Person ; + sh:property [ + sh:path ex:label ; + sh:languageIn ( "en" "no" ) ; + ] . + diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/valid/case1/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case1/query1.rq new file mode 100644 index 000000000..f96b5ba20 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case1/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 a ex:Person ; + ex:label "human"@en, "menneske"@no. + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/valid/case2/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case2/query1.rq new file mode 100644 index 000000000..4f6df8516 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case2/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 a ex:Person ; + ex:label "human"@en. + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/valid/case2/query2.rq b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case2/query2.rq new file mode 100644 index 000000000..00596fbf2 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case2/query2.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 ex:label "menneske"@no. + + +} + diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/valid/case3/query1.rq b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case3/query1.rq new file mode 100644 index 000000000..d1ab87a2d --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case3/query1.rq @@ -0,0 +1,13 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 ex:label "human"@en. +. + +} diff --git a/shacl/src/test/resources/test-cases/languageIn/simple/valid/case3/query2.rq b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case3/query2.rq new file mode 100644 index 000000000..704ada177 --- /dev/null +++ b/shacl/src/test/resources/test-cases/languageIn/simple/valid/case3/query2.rq @@ -0,0 +1,15 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + + +ex:validPerson2 + ex:label "menneske"@no ; + a ex:Person . + +} diff --git a/shacl/src/test/resources/test-cases/pattern/simple/invalid/case6/query1.rq b/shacl/src/test/resources/test-cases/pattern/simple/invalid/case6/query1.rq new file mode 100644 index 000000000..122003009 --- /dev/null +++ b/shacl/src/test/resources/test-cases/pattern/simple/invalid/case6/query1.rq @@ -0,0 +1,14 @@ +PREFIX ex: +PREFIX owl: +PREFIX rdf: +PREFIX rdfs: +PREFIX sh: +PREFIX xsd: + +INSERT DATA { + +ex:validPerson1 a ex:Person ; + ex:uuid 1. + + +}