DashHasValueInConstraintComponent.java
/*******************************************************************************
* Copyright (c) 2020 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.
*
* SPDX-License-Identifier: BSD-3-Clause
*******************************************************************************/
package org.eclipse.rdf4j.sail.shacl.ast.constraintcomponents;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.vocabulary.DASH;
import org.eclipse.rdf4j.sail.shacl.SourceConstraintComponent;
import org.eclipse.rdf4j.sail.shacl.ValidationSettings;
import org.eclipse.rdf4j.sail.shacl.ast.ShaclAstLists;
import org.eclipse.rdf4j.sail.shacl.ast.SparqlFragment;
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher;
import org.eclipse.rdf4j.sail.shacl.ast.StatementMatcher.Variable;
import org.eclipse.rdf4j.sail.shacl.ast.paths.Path;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.AbstractBulkJoinPlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.BulkedExternalLeftOuterJoin;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.EmptyNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.GroupByFilter;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.PlanNodeProvider;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ShiftToPropertyShape;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.TrimToTarget;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.UnBufferedPlanNode;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.Unique;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValidationTuple;
import org.eclipse.rdf4j.sail.shacl.ast.planNodes.ValueInFilter;
import org.eclipse.rdf4j.sail.shacl.ast.targets.EffectiveTarget;
import org.eclipse.rdf4j.sail.shacl.wrapper.data.ConnectionsGroup;
import org.eclipse.rdf4j.sail.shacl.wrapper.data.RdfsSubClassOfReasoner;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;
public class DashHasValueInConstraintComponent extends AbstractConstraintComponent {
final Set<Value> hasValueIn;
public DashHasValueInConstraintComponent(ShapeSource shapeSource, Resource hasValueIn) {
super(hasValueIn);
this.hasValueIn = Collections
.unmodifiableSet(new LinkedHashSet<>(ShaclAstLists.toList(shapeSource, hasValueIn, Value.class)));
}
public DashHasValueInConstraintComponent(DashHasValueInConstraintComponent dashHasValueInConstraintComponent) {
super(dashHasValueInConstraintComponent.getId());
hasValueIn = dashHasValueInConstraintComponent.hasValueIn;
}
@Override
public void toModel(Resource subject, IRI predicate, Model model, Set<Resource> cycleDetection) {
model.add(subject, DASH.hasValueIn, getId());
if (!model.contains(getId(), null, null)) {
ShaclAstLists.listToRdf(hasValueIn, getId(), model);
}
}
@Override
public SourceConstraintComponent getConstraintComponent() {
return SourceConstraintComponent.HasValueInConstraintComponent;
}
@Override
public ConstraintComponent deepClone() {
return new DashHasValueInConstraintComponent(this);
}
@Override
public PlanNode generateTransactionalValidationPlan(ConnectionsGroup connectionsGroup,
ValidationSettings validationSettings,
PlanNodeProvider overrideTargetNode, Scope scope) {
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider = new StatementMatcher.StableRandomVariableProvider();
EffectiveTarget effectiveTarget = getTargetChain().getEffectiveTarget(scope,
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider);
if (scope == Scope.propertyShape) {
Path path = getTargetChain().getPath().get();
PlanNode addedTargets;
if (overrideTargetNode != null) {
addedTargets = effectiveTarget.extend(overrideTargetNode.getPlanNode(), connectionsGroup,
validationSettings.getDataGraph(), scope,
EffectiveTarget.Extend.right,
false, null);
} else {
addedTargets = getAllTargetsIncludingThoseAddedByPath(connectionsGroup, validationSettings, scope,
effectiveTarget, path, true);
}
PlanNode joined = new BulkedExternalLeftOuterJoin(
addedTargets,
connectionsGroup.getBaseConnection(),
validationSettings.getDataGraph(),
path.getTargetQueryFragment(new StatementMatcher.Variable("a"), new StatementMatcher.Variable("c"),
connectionsGroup.getRdfsSubClassOfReasoner(), stableRandomVariableProvider, Set.of()),
(b) -> new ValidationTuple(b.getValue("a"), b.getValue("c"), scope, true,
validationSettings.getDataGraph()),
connectionsGroup, AbstractBulkJoinPlanNode.DEFAULT_VARS);
PlanNode invalidTargets = new GroupByFilter(joined, group -> {
return group.stream().map(ValidationTuple::getValue).noneMatch(hasValueIn::contains);
}, connectionsGroup);
return Unique.getInstance(new TrimToTarget(invalidTargets, connectionsGroup), false, connectionsGroup);
} else if (scope == Scope.nodeShape) {
PlanNode addedTargets;
if (overrideTargetNode != null) {
addedTargets = effectiveTarget.extend(overrideTargetNode.getPlanNode(), connectionsGroup,
validationSettings.getDataGraph(), scope,
EffectiveTarget.Extend.right,
false, null);
} else {
addedTargets = effectiveTarget.getPlanNode(connectionsGroup, validationSettings.getDataGraph(), scope,
false,
null);
}
PlanNode falseNode = new ValueInFilter(addedTargets, hasValueIn, connectionsGroup)
.getFalseNode(UnBufferedPlanNode.class);
return falseNode;
} else {
throw new UnsupportedOperationException("Unknown scope: " + scope);
}
}
@Override
public PlanNode getAllTargetsPlan(ConnectionsGroup connectionsGroup, Resource[] dataGraph, Scope scope,
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider,
ValidationSettings validationSettings) {
if (scope == Scope.propertyShape) {
PlanNode allTargetsPlan = getTargetChain()
.getEffectiveTarget(Scope.nodeShape, connectionsGroup.getRdfsSubClassOfReasoner(),
stableRandomVariableProvider)
.getPlanNode(connectionsGroup, dataGraph, Scope.nodeShape, true, null);
return Unique.getInstance(new ShiftToPropertyShape(allTargetsPlan, connectionsGroup), true,
connectionsGroup);
}
return EmptyNode.getInstance();
}
@Override
public SparqlFragment buildSparqlValidNodes_rsx_targetShape(Variable<Value> subject,
Variable<Value> object,
RdfsSubClassOfReasoner rdfsSubClassOfReasoner, Scope scope,
StatementMatcher.StableRandomVariableProvider stableRandomVariableProvider) {
if (scope == Scope.propertyShape) {
Path path = getTargetChain().getPath().get();
List<StatementMatcher> statementMatchers = new ArrayList<>();
String sparql = hasValueIn
.stream()
.map(value -> {
SparqlFragment targetQueryFragment = path.getTargetQueryFragment(subject, object,
rdfsSubClassOfReasoner, stableRandomVariableProvider, Set.of());
var optimizedStatementMatchers = StatementMatcher.swap(
targetQueryFragment.getStatementMatchers(), object,
new StatementMatcher.Variable(value));
statementMatchers.addAll(optimizedStatementMatchers);
return "BIND(" + stringRepresentationOfValue(value) + " as " + object.asSparqlVariable() + ")\n"
+ targetQueryFragment.getFragment();
})
.collect(
Collectors.joining("} UNION {\n" + VALUES_INJECTION_POINT + "\n",
"{\n" + VALUES_INJECTION_POINT + "\n",
"}"));
return SparqlFragment.bgp(List.of(), sparql, statementMatchers, null);
} else {
String sparql = hasValueIn
.stream()
.map(value -> {
if (value.isIRI()) {
return object.asSparqlVariable() + " = <" + value + ">";
} else if (value.isLiteral()) {
return object.asSparqlVariable() + " = " + value;
}
throw new UnsupportedOperationException(
"value was unsupported type: " + value.getClass().getSimpleName());
})
.reduce((a, b) -> a + " || " + b)
.orElseThrow(() -> new IllegalStateException("hasValueIn was empty"));
return SparqlFragment.filterCondition(List.of(), sparql, List.of());
}
}
@Override
public List<Literal> getDefaultMessage() {
return List.of();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DashHasValueInConstraintComponent that = (DashHasValueInConstraintComponent) o;
return hasValueIn.equals(that.hasValueIn);
}
@Override
public int hashCode() {
return hasValueIn.hashCode() + "DashHasValueInConstraintComponent".hashCode();
}
}