BackwardChainingShapeSource.java
/*******************************************************************************
* Copyright (c) 2022 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.wrapper.shape;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.Statement;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.util.Statements;
import org.eclipse.rdf4j.model.vocabulary.DASH;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.sail.SailConnection;
public class BackwardChainingShapeSource implements ShapeSource {
private final SailConnection connection;
private final Resource[] context;
public BackwardChainingShapeSource(SailConnection connection) {
this(connection, null);
}
private BackwardChainingShapeSource(SailConnection connection, Resource[] context) {
this.connection = connection;
this.context = context;
assert connection.isActive();
}
public BackwardChainingShapeSource withContext(Resource[] context) {
return new BackwardChainingShapeSource(connection, context);
}
@Override
public Resource[] getActiveContexts() {
return context;
}
public Stream<ShapesGraph> getAllShapeContexts() {
assert context != null;
Stream<ShapesGraph> rsxDataAndShapesGraphLink = ShapeSource.getRsxDataAndShapesGraphLink(connection, context);
Stream<ShapesGraph> shaclShapesGraph;
try (var stream = connection.getStatements(null, SHACL.SHAPES_GRAPH, null, false, context).stream()) {
var collect = stream.collect(Collectors.toList()); // consume entire stream to ensure that it can be closed
shaclShapesGraph = collect.stream()
.collect(Collectors.groupingBy(Statement::getSubject))
.entrySet()
.stream()
.map(entry -> new ShapeSource.ShapesGraph(entry.getKey(), entry.getValue()));
}
return Stream.concat(rsxDataAndShapesGraphLink, shaclShapesGraph);
}
public Stream<Resource> getTargetableShape() {
assert context != null;
Stream<Resource> inferred = connection.getStatements(null, RDF.TYPE, RDFS.CLASS, true, context)
.stream()
.map(Statement::getSubject)
.filter(this::isNodeShapeOrPropertyShape);
return Stream
.of(getSubjects(Predicates.TARGET_NODE), getSubjects(Predicates.TARGET_CLASS),
getSubjects(Predicates.TARGET_SUBJECTS_OF), getSubjects(Predicates.TARGET_OBJECTS_OF),
getSubjects(Predicates.TARGET_PROP), getSubjects(Predicates.RSX_targetShape), inferred)
.reduce(Stream::concat)
.get()
.distinct();
}
private boolean isNodeShapeOrPropertyShape(Resource id) {
return connection.hasStatement(id, RDF.TYPE, SHACL.NODE_SHAPE, true, context)
|| connection.hasStatement(id, RDF.TYPE, SHACL.PROPERTY_SHAPE, true, context);
}
public Stream<Resource> getSubjects(Predicates predicate) {
assert context != null;
return connection.getStatements(null, predicate.getIRI(), null, true, context)
.stream()
.map(Statement::getSubject)
.distinct();
}
public Stream<Value> getObjects(Resource subject, Predicates predicate) {
assert context != null;
return connection.getStatements(subject, predicate.getIRI(), null, true, context)
.stream()
.map(Statement::getObject)
.distinct();
}
public Stream<Statement> getAllStatements(Resource id) {
assert context != null;
Stream<Statement> backwardsChained = DASH_CONSTANTS.stream();
if (connection.hasStatement(id, SHACL.PATH, null, true, context)) {
backwardsChained = Stream.concat(
backwardsChained,
Stream.of(Statements.statement(id, RDF.TYPE, SHACL.PROPERTY_SHAPE, null))
);
}
if (connection.hasStatement(id, RDF.TYPE, RDFS.CLASS, true, context) &&
isNodeShapeOrPropertyShape(id)) {
backwardsChained = Stream.concat(
backwardsChained,
Stream.of(Statements.statement(id, SHACL.TARGET_CLASS, id, null))
);
}
return Stream.concat(
connection.getStatements(id, null, null, true, context).stream().map(s -> ((Statement) s)),
backwardsChained
)
.map(Statements::stripContext)
.distinct();
}
public Value getRdfFirst(Resource subject) {
return ShapeSourceHelper.getFirst(connection, subject, context);
}
public Resource getRdfRest(Resource subject) {
return ShapeSourceHelper.getRdfRest(connection, subject, context);
}
public boolean isType(Resource subject, IRI type) {
assert context != null;
if (DASH_CONSTANTS.contains(subject, RDF.TYPE, type)
|| connection.hasStatement(subject, RDF.TYPE, type, true, context)) {
return true;
}
if (!(type == SHACL.NODE_SHAPE || type == SHACL.PROPERTY_SHAPE)) {
if (type.equals(SHACL.NODE_SHAPE)) {
type = SHACL.NODE_SHAPE;
} else if (type.equals(SHACL.PROPERTY_SHAPE)) {
type = SHACL.PROPERTY_SHAPE;
}
}
if (type == SHACL.PROPERTY_SHAPE) {
return connection.hasStatement(subject, SHACL.PATH, null, true, context);
} else if (type == SHACL.NODE_SHAPE) {
if (connection.hasStatement(subject, SHACL.PATH, null, true, context)) {
return false;
}
if (connection.hasStatement(null, SHACL.NODE, subject, true, context)) {
return true;
}
try (Stream<? extends Statement> stream = connection.getStatements(subject, null, null, true, context)
.stream()) {
return stream
.map(Statement::getPredicate)
.map(Value::stringValue)
.anyMatch(predicate -> predicate.startsWith(SHACL.NAMESPACE)
|| predicate.startsWith(DASH.NAMESPACE));
}
} else {
return false;
}
}
@Override
public void close() {
// we don't close the provided connection
}
}