ShaclValidator.java
/*******************************************************************************
* Copyright (c) 2023 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;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.rdf4j.common.annotation.Experimental;
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.sail.Sail;
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.shacl.ast.ContextWithShape;
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
import org.eclipse.rdf4j.sail.shacl.results.ValidationReport;
import org.eclipse.rdf4j.sail.shacl.results.lazy.LazyValidationReport;
import org.eclipse.rdf4j.sail.shacl.results.lazy.ValidationResultIterator;
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.data.VerySimpleRdfsBackwardsChainingConnection;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.CombinedShapeSource;
import org.eclipse.rdf4j.sail.shacl.wrapper.shape.ShapeSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Experimental
public class ShaclValidator {
private static final Resource[] ALL_CONTEXTS = {};
private static final Logger logger = LoggerFactory.getLogger(ShaclValidator.class);
// tests can write to this field using reflection
@SuppressWarnings("FieldMayBeFinal")
private static Resource[] SHAPE_CONTEXTS = ALL_CONTEXTS;
public static ValidationReport validate(Sail dataRepo, Sail shapesRepo) {
List<ContextWithShape> shapes;
try (SailConnection shapesConnection = shapesRepo.getConnection()) {
shapesConnection.begin(IsolationLevels.NONE);
try (ShapeSource shapeSource = new CombinedShapeSource(shapesConnection,
shapesConnection)) {
Stream<ShapeSource.ShapesGraph> allShapeContexts = shapeSource
.withContext(SHAPE_CONTEXTS)
.getAllShapeContexts();
if (SHAPE_CONTEXTS.length == 0) {
allShapeContexts = Stream.concat(allShapeContexts,
Stream.of(new ShapeSource.ShapesGraph(RDF4J.NIL)));
}
List<ContextWithShape> parsed = allShapeContexts
.map(context -> Shape.Factory.parse(shapeSource.withContext(context.getShapesGraph()), context,
new Shape.ParseSettings(true, true)))
.flatMap(List::stream)
.collect(Collectors.toList());
shapes = Shape.Factory.getShapes(parsed).stream().distinct().collect(Collectors.toList());
if (logger.isDebugEnabled()) {
for (ContextWithShape shape : shapes) {
logger.debug("Using data graph(s) {} and shape graph(s) {} with shape {}",
Arrays.toString(shape.getDataGraph()), Arrays.toString(shape.getShapeGraph()),
shape.getShape());
}
}
}
shapesConnection.commit();
} catch (Throwable e) {
logger.warn("Failed to read shapes", e);
throw e;
}
try (SailConnection dataRepoConnection = dataRepo.getConnection()) {
RdfsSubClassOfReasoner reasoner;
try (SailConnection shapesConnection = shapesRepo.getConnection()) {
reasoner = RdfsSubClassOfReasoner.createReasoner(
dataRepoConnection, shapesConnection,
new ValidationSettings(ALL_CONTEXTS, false, true, false));
}
VerySimpleRdfsBackwardsChainingConnection verySimpleRdfsBackwardsChainingConnection = new VerySimpleRdfsBackwardsChainingConnection(
dataRepoConnection, reasoner);
return performValidation(shapes, new ConnectionsGroup(verySimpleRdfsBackwardsChainingConnection, null,
null, null, new Stats(), () -> reasoner,
new ShaclSailConnection.Settings(true, true, true, IsolationLevels.NONE), true));
} catch (Throwable e) {
logger.warn("Failed to validate shapes", e);
throw e;
}
}
private static ValidationReport performValidation(List<ContextWithShape> shapes,
ConnectionsGroup connectionsGroup) {
List<ValidationResultIterator> collect = shapes
.stream()
.map(contextWithShape -> new ShapeValidationContainer(
contextWithShape.getShape(),
() -> contextWithShape.getShape()
.generatePlans(connectionsGroup,
new ValidationSettings(contextWithShape.getDataGraph(), false, true, false)),
false, false, 1000, false, false, logger,
connectionsGroup)
)
.filter(ShapeValidationContainer::hasPlanNode)
.map(ShapeValidationContainer::performValidation)
.collect(Collectors.toList());
return new LazyValidationReport(collect, 10000);
}
}