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
******************************************************************************/
// Some portions generated by Codex
package org.eclipse.rdf4j.sail.shacl;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import org.eclipse.rdf4j.common.annotation.Experimental;
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.model.vocabulary.RSX;
import org.eclipse.rdf4j.model.vocabulary.SESAME;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.repository.sail.SailRepository;
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.RDFParseException;
import org.eclipse.rdf4j.rio.Rio;
import org.eclipse.rdf4j.sail.InterruptedSailException;
import org.eclipse.rdf4j.sail.Sail;
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.SailException;
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.eclipse.rdf4j.sail.shacl.ast.Cache;
import org.eclipse.rdf4j.sail.shacl.ast.ContextWithShape;
import org.eclipse.rdf4j.sail.shacl.ast.Shape;
import org.eclipse.rdf4j.sail.shacl.config.ShaclSailConfig;
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);
/**
* Create a new builder for configuring a SHACL validator.
*
* @return a new builder instance
*/
public static Builder builder() {
return new Builder();
}
/**
* Create a builder seeded from an existing {@link ShaclSail}.
*
* @param shaclSail the configured SHACL sail supplying shapes and settings
* @return a builder preconfigured from the supplied sail
*/
public static BuilderWithShapes from(ShaclSail shaclSail) {
return new BuilderWithShapes(Builder.settingsFrom(shaclSail), shaclSail);
}
@FunctionalInterface
private interface SailLoader {
void load(SailRepositoryConnection connection) throws IOException, RDFParseException;
}
private static Sail loadShapes(String description, SailLoader loader) {
return loadSail("SHACL shapes", description, loader);
}
private static Sail loadData(String description, SailLoader loader) {
return loadSail("data", description, loader);
}
private static Sail loadSail(String kind, String description, SailLoader loader) {
SailRepository repo = new SailRepository(new MemoryStore());
repo.init();
try (SailRepositoryConnection connection = repo.getConnection()) {
connection.begin(IsolationLevels.NONE);
loader.load(connection);
connection.commit();
} catch (IOException | RDFParseException e) {
throw new SailException("Failed to read " + kind + " from " + description, e);
} catch (RuntimeException e) {
if (e instanceof SailException) {
throw e;
}
throw new SailException("Failed to read " + kind + " from " + description, e);
}
return repo.getSail();
}
private static RDFFormat detectRdfFormat(String description, String... candidates) {
for (String candidate : candidates) {
if (candidate == null || candidate.isBlank()) {
continue;
}
var format = Rio.getParserFormatForFileName(candidate);
if (format.isPresent()) {
return format.get();
}
}
throw new SailException(
"Could not determine RDF format for " + description + ". Provide RDFFormat explicitly.");
}
public static class Builder extends InternalBuilder<Builder> implements Cloneable {
private Builder() {
}
/**
* Create a builder with settings copied from an existing {@link ShaclSail}.
*
* @param shaclSail the configured SHACL sail
* @return a builder initialized from the supplied sail
*/
public static Builder settingsFrom(ShaclSail shaclSail) {
Builder builder = new Builder();
builder.setShapesGraphs(shaclSail.getShapesGraphs());
builder.setParallelValidation(shaclSail.isParallelValidation());
builder.setLogValidationPlans(shaclSail.isLogValidationPlans());
builder.setLogValidationViolations(shaclSail.isLogValidationViolations());
builder.setGlobalLogValidationExecution(shaclSail.isGlobalLogValidationExecution());
builder.setCacheSelectNodes(shaclSail.isCacheSelectNodes());
builder.setRdfsSubClassReasoning(shaclSail.isRdfsSubClassReasoning());
builder.setSerializableValidation(shaclSail.isSerializableValidation());
builder.setPerformanceLogging(shaclSail.isPerformanceLogging());
builder.setEclipseRdf4jShaclExtensions(shaclSail.isEclipseRdf4jShaclExtensions());
builder.setDashDataShapes(shaclSail.isDashDataShapes());
builder.setValidationResultsLimitTotal(shaclSail.getValidationResultsLimitTotal());
builder.setValidationResultsLimitPerConstraint(shaclSail.getValidationResultsLimitPerConstraint());
builder.setTransactionalValidationLimit(shaclSail.getTransactionalValidationLimit());
if (shaclSail.isValidationEnabled()) {
builder.enableValidation();
} else {
builder.disableValidation();
}
return builder;
}
/**
* Use the supplied SHACL shapes sail for validation.
*
* @param shapes the shapes sail
* @return a builder that validates with the supplied shapes
*/
public BuilderWithShapes withShapes(Sail shapes) {
return new BuilderWithShapes(this, shapes);
}
/**
* Load SHACL shapes from a file using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(File shapesFile, String baseURI) {
Objects.requireNonNull(shapesFile, "shapesFile");
RDFFormat format = detectRdfFormat("file " + shapesFile, shapesFile.getName(), baseURI);
return withShapes(shapesFile, baseURI, format);
}
/**
* Load SHACL shapes from a file using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(File shapesFile) {
Objects.requireNonNull(shapesFile, "shapesFile");
RDFFormat format = detectRdfFormat("file " + shapesFile, shapesFile.getName());
return withShapes(shapesFile, format);
}
/**
* Load SHACL shapes from a path using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(Path shapesPath, String baseURI) {
Objects.requireNonNull(shapesPath, "shapesPath");
RDFFormat format = detectRdfFormat("path " + shapesPath, shapesPath.getFileName().toString(), baseURI);
return withShapes(shapesPath, baseURI, format);
}
/**
* Load SHACL shapes from a path using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(Path shapesPath) {
Objects.requireNonNull(shapesPath, "shapesPath");
RDFFormat format = detectRdfFormat("path " + shapesPath, shapesPath.getFileName().toString());
return withShapes(shapesPath, format);
}
/**
* Load SHACL shapes from a URL using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(URL shapesUrl, String baseURI) {
Objects.requireNonNull(shapesUrl, "shapesUrl");
RDFFormat format = detectRdfFormat("URL " + shapesUrl, shapesUrl.getPath(), baseURI);
return withShapes(shapesUrl, baseURI, format);
}
/**
* Load SHACL shapes from a URL using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(URL shapesUrl) {
Objects.requireNonNull(shapesUrl, "shapesUrl");
RDFFormat format = detectRdfFormat("URL " + shapesUrl, shapesUrl.getPath());
return withShapes(shapesUrl, format);
}
/**
* Load SHACL shapes from an input stream using an auto-detected RDF format. The input stream is not closed by
* this method.
*/
public BuilderWithShapes withShapes(InputStream shapesInputStream, String baseURI) {
Objects.requireNonNull(shapesInputStream, "shapesInputStream");
RDFFormat format = detectRdfFormat("input stream", baseURI);
return withShapes(shapesInputStream, baseURI, format);
}
/**
* Load SHACL shapes from RDF content in a string using an auto-detected RDF format.
*/
public BuilderWithShapes withShapes(String shapes, String baseURI) {
Objects.requireNonNull(shapes, "shapes");
RDFFormat format = detectRdfFormat("string content", baseURI);
return withShapes(shapes, baseURI, format);
}
/**
* Load SHACL shapes from a file using the supplied base URI and RDF format.
*/
public BuilderWithShapes withShapes(File shapesFile, String baseURI, RDFFormat format) {
Objects.requireNonNull(shapesFile, "shapesFile");
Objects.requireNonNull(format, "rdfFormat");
Sail shapes = loadShapes("file " + shapesFile, connection -> connection.add(shapesFile, baseURI, format));
return withShapes(shapes);
}
/**
* Load SHACL shapes from a file using the supplied RDF format.
*/
public BuilderWithShapes withShapes(File shapesFile, RDFFormat format) {
Objects.requireNonNull(shapesFile, "shapesFile");
Objects.requireNonNull(format, "rdfFormat");
Sail shapes = loadShapes("file " + shapesFile, connection -> connection.add(shapesFile, format));
return withShapes(shapes);
}
/**
* Load SHACL shapes from a path using the supplied base URI and RDF format.
*/
public BuilderWithShapes withShapes(Path shapesPath, String baseURI, RDFFormat format) {
Objects.requireNonNull(shapesPath, "shapesPath");
Objects.requireNonNull(format, "rdfFormat");
return withShapes(shapesPath.toFile(), baseURI, format);
}
/**
* Load SHACL shapes from a path using the supplied RDF format.
*/
public BuilderWithShapes withShapes(Path shapesPath, RDFFormat format) {
Objects.requireNonNull(shapesPath, "shapesPath");
Objects.requireNonNull(format, "rdfFormat");
return withShapes(shapesPath.toFile(), format);
}
/**
* Load SHACL shapes from a URL using the supplied base URI and RDF format.
*/
public BuilderWithShapes withShapes(URL shapesUrl, String baseURI, RDFFormat format) {
Objects.requireNonNull(shapesUrl, "shapesUrl");
Objects.requireNonNull(format, "rdfFormat");
Sail shapes = loadShapes("URL " + shapesUrl, connection -> connection.add(shapesUrl, baseURI, format));
return withShapes(shapes);
}
/**
* Load SHACL shapes from a URL using the supplied RDF format.
*/
public BuilderWithShapes withShapes(URL shapesUrl, RDFFormat format) {
Objects.requireNonNull(shapesUrl, "shapesUrl");
Objects.requireNonNull(format, "rdfFormat");
Sail shapes = loadShapes("URL " + shapesUrl, connection -> connection.add(shapesUrl, format));
return withShapes(shapes);
}
/**
* Load SHACL shapes from an input stream using the supplied base URI and RDF format. The input stream is not
* closed by this method.
*/
public BuilderWithShapes withShapes(InputStream shapesInputStream, String baseURI, RDFFormat format) {
Objects.requireNonNull(shapesInputStream, "shapesInputStream");
Objects.requireNonNull(format, "rdfFormat");
Sail shapes = loadShapes("input stream", connection -> connection.add(shapesInputStream, baseURI, format));
return withShapes(shapes);
}
/**
* Load SHACL shapes from an input stream using the supplied RDF format. The input stream is not closed by this
* method.
*/
public BuilderWithShapes withShapes(InputStream shapesInputStream, RDFFormat format) {
Objects.requireNonNull(shapesInputStream, "shapesInputStream");
Objects.requireNonNull(format, "rdfFormat");
Sail shapes = loadShapes("input stream", connection -> connection.add(shapesInputStream, format));
return withShapes(shapes);
}
/**
* Load SHACL shapes from RDF content in a string using the supplied base URI and RDF format.
*/
public BuilderWithShapes withShapes(String shapes, String baseURI, RDFFormat format) {
Objects.requireNonNull(shapes, "shapes");
Objects.requireNonNull(format, "rdfFormat");
Sail shapesSail = loadShapes("string content",
connection -> connection.add(new StringReader(shapes), baseURI, format));
return withShapes(shapesSail);
}
/**
* Load SHACL shapes from RDF content in a string using the supplied RDF format.
*/
public BuilderWithShapes withShapes(String shapes, RDFFormat format) {
Objects.requireNonNull(shapes, "shapes");
Objects.requireNonNull(format, "rdfFormat");
Sail shapesSail = loadShapes("string content",
connection -> connection.add(new StringReader(shapes), format));
return withShapes(shapesSail);
}
/**
* Build a validator that expects shapes to be supplied at validation time.
*
* @return a validator configured with the current settings
*/
public Validator build() {
return new Validator(this);
}
/**
* {@inheritDoc}
*/
@Override
public Builder clone() {
return (Builder) super.clone();
}
}
static class InternalBuilder<T extends InternalBuilder<T>> implements Cloneable {
private Resource[] shapeContexts = null;
private boolean parallelValidation = true;
private boolean logValidationPlans = false;
private boolean logValidationViolations = false;
private boolean validationEnabled = true;
private boolean cacheSelectNodes = true;
private boolean globalLogValidationExecution = false;
private boolean rdfsSubClassReasoning = true;
private boolean performanceLogging = false;
private boolean serializableValidation = false;
private boolean eclipseRdf4jShaclExtensions = true;
private boolean dashDataShapes = true;
private long validationResultsLimitTotal = ShaclSailConfig.VALIDATION_RESULTS_LIMIT_TOTAL_DEFAULT;
private long validationResultsLimitPerConstraint = ShaclSailConfig.VALIDATION_RESULTS_LIMIT_PER_CONSTRAINT_DEFAULT;
private long transactionalValidationLimit = ShaclSailConfig.TRANSACTIONAL_VALIDATION_LIMIT_DEFAULT;
private long validationTimeoutMillis = -1;
private boolean sparqlValidation = !"false"
.equalsIgnoreCase(System.getProperty("org.eclipse.rdf4j.sail.shacl.sparqlValidation"));
void setAll(InternalBuilder<?> other) {
this.shapeContexts = other.shapeContexts == null ? null : other.shapeContexts.clone();
this.parallelValidation = other.parallelValidation;
this.logValidationPlans = other.logValidationPlans;
this.logValidationViolations = other.logValidationViolations;
this.validationEnabled = other.validationEnabled;
this.cacheSelectNodes = other.cacheSelectNodes;
this.globalLogValidationExecution = other.globalLogValidationExecution;
this.rdfsSubClassReasoning = other.rdfsSubClassReasoning;
this.performanceLogging = other.performanceLogging;
this.serializableValidation = other.serializableValidation;
this.eclipseRdf4jShaclExtensions = other.eclipseRdf4jShaclExtensions;
this.dashDataShapes = other.dashDataShapes;
this.validationResultsLimitTotal = other.validationResultsLimitTotal;
this.validationResultsLimitPerConstraint = other.validationResultsLimitPerConstraint;
this.transactionalValidationLimit = other.transactionalValidationLimit;
this.validationTimeoutMillis = other.validationTimeoutMillis;
this.sparqlValidation = other.sparqlValidation;
}
/**
* Set the contexts to use when discovering SHACL shapes.
*
* @param shapeContexts contexts to scan, or {@code null} to scan all contexts
* @return this builder instance
*/
public T shapeContexts(Resource... shapeContexts) {
this.shapeContexts = shapeContexts == null ? null : shapeContexts.clone();
return (T) this;
}
/**
* Enable or disable global logging of validation execution.
*
* @param loggingEnabled whether to enable logging
* @return this builder instance
*/
public T setGlobalLogValidationExecution(boolean loggingEnabled) {
this.globalLogValidationExecution = loggingEnabled;
return (T) this;
}
/**
* Enable or disable logging of validation violations.
*
* @param logValidationViolations whether to log violations
* @return this builder instance
*/
public T setLogValidationViolations(boolean logValidationViolations) {
this.logValidationViolations = logValidationViolations;
return (T) this;
}
/**
* Enable or disable parallel validation.
*
* @param parallelValidation whether to run validation in parallel
* @return this builder instance
*/
public T setParallelValidation(boolean parallelValidation) {
this.parallelValidation = parallelValidation;
return (T) this;
}
/**
* Enable or disable caching of select nodes during validation.
*
* @param cacheSelectNodes whether to cache select nodes
* @return this builder instance
*/
public T setCacheSelectNodes(boolean cacheSelectNodes) {
this.cacheSelectNodes = cacheSelectNodes;
return (T) this;
}
/**
* Enable or disable RDFS subclass reasoning during validation.
*
* @param rdfsSubClassReasoning whether to enable subclass reasoning
* @return this builder instance
*/
public T setRdfsSubClassReasoning(boolean rdfsSubClassReasoning) {
this.rdfsSubClassReasoning = rdfsSubClassReasoning;
return (T) this;
}
/**
* Disable SHACL validation entirely.
*
* @return this builder instance
*/
public T disableValidation() {
this.validationEnabled = false;
return (T) this;
}
/**
* Enable SHACL validation.
*
* @return this builder instance
*/
public T enableValidation() {
this.validationEnabled = true;
return (T) this;
}
/**
* Enable or disable logging of validation plans.
*
* @param logValidationPlans whether to log validation plans
* @return this builder instance
*/
public T setLogValidationPlans(boolean logValidationPlans) {
this.logValidationPlans = logValidationPlans;
return (T) this;
}
/**
* Enable or disable performance logging during validation.
*
* @param performanceLogging whether to log performance details
* @return this builder instance
*/
public T setPerformanceLogging(boolean performanceLogging) {
this.performanceLogging = performanceLogging;
return (T) this;
}
/**
* Enable or disable serializable validation mode.
*
* @param serializableValidation whether to enable serializable validation
* @return this builder instance
*/
public T setSerializableValidation(boolean serializableValidation) {
this.serializableValidation = serializableValidation;
return (T) this;
}
/**
* Enable or disable RDF4J SHACL extensions.
*
* @param eclipseRdf4jShaclExtensions whether to enable the extensions
* @return this builder instance
*/
public T setEclipseRdf4jShaclExtensions(boolean eclipseRdf4jShaclExtensions) {
this.eclipseRdf4jShaclExtensions = eclipseRdf4jShaclExtensions;
return (T) this;
}
/**
* Enable or disable DASH data shapes support.
*
* @param dashDataShapes whether to enable DASH data shapes
* @return this builder instance
*/
public T setDashDataShapes(boolean dashDataShapes) {
this.dashDataShapes = dashDataShapes;
return (T) this;
}
/**
* Set the maximum number of validation results per constraint.
*
* @param validationResultsLimitPerConstraint limit per constraint, or a negative value to defer to the total
* limit
* @return this builder instance
*/
public T setValidationResultsLimitPerConstraint(long validationResultsLimitPerConstraint) {
this.validationResultsLimitPerConstraint = validationResultsLimitPerConstraint;
return (T) this;
}
/**
* Set the total maximum number of validation results in a report.
*
* @param validationResultsLimitTotal total limit, or a negative value for no limit
* @return this builder instance
*/
public T setValidationResultsLimitTotal(long validationResultsLimitTotal) {
this.validationResultsLimitTotal = validationResultsLimitTotal;
return (T) this;
}
/**
* Set the transactional validation limit.
*
* @param transactionalValidationLimit the transactional validation limit
* @return this builder instance
*/
public T setTransactionalValidationLimit(long transactionalValidationLimit) {
this.transactionalValidationLimit = transactionalValidationLimit;
return (T) this;
}
/**
* Set the validation timeout in milliseconds.
*
* @param validationTimeoutMillis timeout in milliseconds, or a negative value to disable the timeout
* @return this builder instance
*/
public T setValidationTimeoutMillis(long validationTimeoutMillis) {
this.validationTimeoutMillis = validationTimeoutMillis;
return (T) this;
}
/**
* Set the SHACL shapes graphs to use when discovering shapes.
*
* <p>
* Use {@link RDF4J#NIL} or {@link SESAME#NIL} to indicate the default graph.
* </p>
*
* @param shapesGraphs the shapes graphs, or {@code null} to scan all graphs
* @return this builder instance
*/
public T setShapesGraphs(Set<IRI> shapesGraphs) {
if (shapesGraphs == null) {
this.shapeContexts = null;
return (T) this;
}
if (shapesGraphs.isEmpty()) {
this.shapeContexts = ALL_CONTEXTS;
return (T) this;
}
this.shapeContexts = shapesGraphs.stream()
.map(g -> {
if (RDF4J.NIL.equals(g) || SESAME.NIL.equals(g)) {
return null;
}
return g;
})
.toArray(Resource[]::new);
return (T) this;
}
private long getEffectiveValidationResultsLimitPerConstraint() {
if (validationResultsLimitPerConstraint < 0) {
return validationResultsLimitTotal;
}
if (validationResultsLimitTotal >= 0) {
return Math.min(validationResultsLimitTotal, validationResultsLimitPerConstraint);
}
return validationResultsLimitPerConstraint;
}
/**
* {@inheritDoc}
*/
@Override
public InternalBuilder<T> clone() {
try {
InternalBuilder<T> clone = (InternalBuilder<T>) super.clone();
clone.shapeContexts = shapeContexts == null ? null : shapeContexts.clone();
return clone;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public static class BuilderWithShapes extends InternalBuilder<BuilderWithShapes> implements Cloneable {
/**
* The shapes sail used for validation.
*/
public Sail shapes;
private BuilderWithShapes(Builder builder, Sail shapes) {
this.shapes = shapes;
setAll(builder);
}
/**
* Build a validator that validates data against the configured shapes.
*
* @return a validator bound to the shapes sail
*/
public ValidatorWithShapes build() {
return new ValidatorWithShapes(this);
}
/**
* {@inheritDoc}
*/
@Override
public BuilderWithShapes clone() {
BuilderWithShapes clone = (BuilderWithShapes) super.clone();
// TODO: copy mutable state here, so the clone can't change the internals of the original
return clone;
}
}
public static class ValidatorWithShapes {
private final BuilderWithShapes builderWithShapes;
/**
* Create a validator that uses the supplied builder configuration.
*
* @param builderWithShapes configured builder with shapes
*/
public ValidatorWithShapes(BuilderWithShapes builderWithShapes) {
this.builderWithShapes = builderWithShapes.clone();
}
private ValidationReport validateLoadedData(String description, SailLoader loader) {
Sail data = loadData(description, loader);
return validate(data);
}
/**
* Validate the supplied data sail against the configured shapes.
*
* @param dataRepo data sail to validate
* @return validation report
*/
public ValidationReport validate(Sail dataRepo) {
return validateInternal(dataRepo, builderWithShapes.shapes, builderWithShapes);
}
/**
* Load data from a file and validate it against the configured shapes.
*
* @param dataFile data file to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(File dataFile, String baseURI, RDFFormat format) {
Objects.requireNonNull(dataFile, "dataFile");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("file " + dataFile, connection -> connection.add(dataFile, baseURI, format));
}
/**
* Load data from a path and validate it against the configured shapes.
*
* @param dataPath data path to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Path dataPath, String baseURI, RDFFormat format) {
Objects.requireNonNull(dataPath, "dataPath");
return validate(dataPath.toFile(), baseURI, format);
}
/**
* Load data from a URL and validate it against the configured shapes.
*
* @param dataUrl data URL to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(URL dataUrl, String baseURI, RDFFormat format) {
Objects.requireNonNull(dataUrl, "dataUrl");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("URL " + dataUrl, connection -> connection.add(dataUrl, baseURI, format));
}
/**
* Load data from an input stream and validate it against the configured shapes. The input stream is not closed
* by this method.
*
* @param dataInputStream data input stream to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(InputStream dataInputStream, String baseURI, RDFFormat format) {
Objects.requireNonNull(dataInputStream, "dataInputStream");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("input stream", connection -> connection.add(dataInputStream, baseURI, format));
}
/**
* Load data from RDF content in a string and validate it against the configured shapes.
*
* @param data RDF content to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(String data, String baseURI, RDFFormat format) {
Objects.requireNonNull(data, "data");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("string content",
connection -> connection.add(new StringReader(data), baseURI, format));
}
/**
* Load data from a file using an auto-detected RDF format and validate it against the configured shapes.
*
* @param dataFile data file to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(File dataFile, String baseURI) {
Objects.requireNonNull(dataFile, "dataFile");
RDFFormat format = detectRdfFormat("file " + dataFile, dataFile.getName(), baseURI);
return validate(dataFile, baseURI, format);
}
/**
* Load data from a path using an auto-detected RDF format and validate it against the configured shapes.
*
* @param dataPath data path to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(Path dataPath, String baseURI) {
Objects.requireNonNull(dataPath, "dataPath");
RDFFormat format = detectRdfFormat("path " + dataPath, dataPath.getFileName().toString(), baseURI);
return validate(dataPath, baseURI, format);
}
/**
* Load data from a URL using an auto-detected RDF format and validate it against the configured shapes.
*
* @param dataUrl data URL to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(URL dataUrl, String baseURI) {
Objects.requireNonNull(dataUrl, "dataUrl");
RDFFormat format = detectRdfFormat("URL " + dataUrl, dataUrl.getPath(), baseURI);
return validate(dataUrl, baseURI, format);
}
/**
* Load data from an input stream using an auto-detected RDF format and validate it against the configured
* shapes. The input stream is not closed by this method.
*
* @param dataInputStream data input stream to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(InputStream dataInputStream, String baseURI) {
Objects.requireNonNull(dataInputStream, "dataInputStream");
RDFFormat format = detectRdfFormat("input stream", baseURI);
return validate(dataInputStream, baseURI, format);
}
/**
* Load data from RDF content in a string using an auto-detected RDF format and validate it against the
* configured shapes.
*
* @param data RDF content to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(String data, String baseURI) {
Objects.requireNonNull(data, "data");
RDFFormat format = detectRdfFormat("string content", baseURI);
return validate(data, baseURI, format);
}
/**
* Load data from a file using the supplied RDF format and validate it against the configured shapes.
*
* @param dataFile data file to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(File dataFile, RDFFormat format) {
Objects.requireNonNull(dataFile, "dataFile");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("file " + dataFile, connection -> connection.add(dataFile, format));
}
/**
* Load data from a path using the supplied RDF format and validate it against the configured shapes.
*
* @param dataPath data path to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Path dataPath, RDFFormat format) {
Objects.requireNonNull(dataPath, "dataPath");
return validate(dataPath.toFile(), format);
}
/**
* Load data from a URL using the supplied RDF format and validate it against the configured shapes.
*
* @param dataUrl data URL to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(URL dataUrl, RDFFormat format) {
Objects.requireNonNull(dataUrl, "dataUrl");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("URL " + dataUrl, connection -> connection.add(dataUrl, format));
}
/**
* Load data from an input stream using the supplied RDF format and validate it against the configured shapes.
* The input stream is not closed by this method.
*
* @param dataInputStream data input stream to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(InputStream dataInputStream, RDFFormat format) {
Objects.requireNonNull(dataInputStream, "dataInputStream");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("input stream", connection -> connection.add(dataInputStream, format));
}
/**
* Load data from RDF content in a string using the supplied RDF format and validate it against the configured
* shapes.
*
* @param data RDF content to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(String data, RDFFormat format) {
Objects.requireNonNull(data, "data");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("string content", connection -> connection.add(new StringReader(data), format));
}
/**
* Load data from a file using an auto-detected RDF format and validate it against the configured shapes.
*
* @param dataFile data file to parse
* @return validation report
*/
public ValidationReport validate(File dataFile) {
Objects.requireNonNull(dataFile, "dataFile");
RDFFormat format = detectRdfFormat("file " + dataFile, dataFile.getName());
return validate(dataFile, format);
}
/**
* Load data from a path using an auto-detected RDF format and validate it against the configured shapes.
*
* @param dataPath data path to parse
* @return validation report
*/
public ValidationReport validate(Path dataPath) {
Objects.requireNonNull(dataPath, "dataPath");
RDFFormat format = detectRdfFormat("path " + dataPath, dataPath.getFileName().toString());
return validate(dataPath, format);
}
/**
* Load data from a URL using an auto-detected RDF format and validate it against the configured shapes.
*
* @param dataUrl data URL to parse
* @return validation report
*/
public ValidationReport validate(URL dataUrl) {
Objects.requireNonNull(dataUrl, "dataUrl");
RDFFormat format = detectRdfFormat("URL " + dataUrl, dataUrl.getPath());
return validate(dataUrl, format);
}
}
public static class Validator {
private final Builder builder;
/**
* Create a validator that uses the supplied builder configuration.
*
* @param builder configured builder
*/
public Validator(Builder builder) {
this.builder = builder.clone();
}
private ValidationReport validateLoadedData(String description, SailLoader loader, Sail shapesRepo) {
Objects.requireNonNull(shapesRepo, "shapesRepo");
Sail data = loadData(description, loader);
return validate(data, shapesRepo);
}
/**
* Validate the supplied data sail against the supplied shapes sail.
*
* @param dataRepo data sail to validate
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, Sail shapesRepo) {
return validateInternal(dataRepo, shapesRepo, builder);
}
/**
* Load data from a file and validate it against the supplied shapes sail.
*
* @param dataFile data file to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(File dataFile, String baseURI, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataFile, "dataFile");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("file " + dataFile, connection -> connection.add(dataFile, baseURI, format),
shapesRepo);
}
/**
* Load data from a path and validate it against the supplied shapes sail.
*
* @param dataPath data path to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(Path dataPath, String baseURI, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataPath, "dataPath");
return validate(dataPath.toFile(), baseURI, format, shapesRepo);
}
/**
* Load data from a URL and validate it against the supplied shapes sail.
*
* @param dataUrl data URL to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(URL dataUrl, String baseURI, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataUrl, "dataUrl");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("URL " + dataUrl, connection -> connection.add(dataUrl, baseURI, format),
shapesRepo);
}
/**
* Load data from an input stream and validate it against the supplied shapes sail. The input stream is not
* closed by this method.
*
* @param dataInputStream data input stream to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(InputStream dataInputStream, String baseURI, RDFFormat format,
Sail shapesRepo) {
Objects.requireNonNull(dataInputStream, "dataInputStream");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("input stream", connection -> connection.add(dataInputStream, baseURI, format),
shapesRepo);
}
/**
* Load data from RDF content in a string and validate it against the supplied shapes sail.
*
* @param data RDF content to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(String data, String baseURI, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(data, "data");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("string content",
connection -> connection.add(new StringReader(data), baseURI, format), shapesRepo);
}
/**
* Load data from a file using an auto-detected RDF format and validate it against the supplied shapes sail.
*
* @param dataFile data file to parse
* @param baseURI base URI for resolving relative IRIs
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(File dataFile, String baseURI, Sail shapesRepo) {
Objects.requireNonNull(dataFile, "dataFile");
RDFFormat format = detectRdfFormat("file " + dataFile, dataFile.getName(), baseURI);
return validate(dataFile, baseURI, format, shapesRepo);
}
/**
* Load data from a path using an auto-detected RDF format and validate it against the supplied shapes sail.
*
* @param dataPath data path to parse
* @param baseURI base URI for resolving relative IRIs
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(Path dataPath, String baseURI, Sail shapesRepo) {
Objects.requireNonNull(dataPath, "dataPath");
RDFFormat format = detectRdfFormat("path " + dataPath, dataPath.getFileName().toString(), baseURI);
return validate(dataPath, baseURI, format, shapesRepo);
}
/**
* Load data from a URL using an auto-detected RDF format and validate it against the supplied shapes sail.
*
* @param dataUrl data URL to parse
* @param baseURI base URI for resolving relative IRIs
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(URL dataUrl, String baseURI, Sail shapesRepo) {
Objects.requireNonNull(dataUrl, "dataUrl");
RDFFormat format = detectRdfFormat("URL " + dataUrl, dataUrl.getPath(), baseURI);
return validate(dataUrl, baseURI, format, shapesRepo);
}
/**
* Load data from an input stream using an auto-detected RDF format and validate it against the supplied shapes
* sail. The input stream is not closed by this method.
*
* @param dataInputStream data input stream to parse
* @param baseURI base URI for resolving relative IRIs
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(InputStream dataInputStream, String baseURI, Sail shapesRepo) {
Objects.requireNonNull(dataInputStream, "dataInputStream");
RDFFormat format = detectRdfFormat("input stream", baseURI);
return validate(dataInputStream, baseURI, format, shapesRepo);
}
/**
* Load data from RDF content in a string using an auto-detected RDF format and validate it against the supplied
* shapes sail.
*
* @param data RDF content to parse
* @param baseURI base URI for resolving relative IRIs
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(String data, String baseURI, Sail shapesRepo) {
Objects.requireNonNull(data, "data");
RDFFormat format = detectRdfFormat("string content", baseURI);
return validate(data, baseURI, format, shapesRepo);
}
/**
* Load data from a file using the supplied RDF format and validate it against the supplied shapes sail.
*
* @param dataFile data file to parse
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(File dataFile, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataFile, "dataFile");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("file " + dataFile, connection -> connection.add(dataFile, format), shapesRepo);
}
/**
* Load data from a path using the supplied RDF format and validate it against the supplied shapes sail.
*
* @param dataPath data path to parse
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(Path dataPath, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataPath, "dataPath");
return validate(dataPath.toFile(), format, shapesRepo);
}
/**
* Load data from a URL using the supplied RDF format and validate it against the supplied shapes sail.
*
* @param dataUrl data URL to parse
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(URL dataUrl, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataUrl, "dataUrl");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("URL " + dataUrl, connection -> connection.add(dataUrl, format), shapesRepo);
}
/**
* Load data from an input stream using the supplied RDF format and validate it against the supplied shapes
* sail. The input stream is not closed by this method.
*
* @param dataInputStream data input stream to parse
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(InputStream dataInputStream, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(dataInputStream, "dataInputStream");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("input stream", connection -> connection.add(dataInputStream, format),
shapesRepo);
}
/**
* Load data from RDF content in a string using the supplied RDF format and validate it against the supplied
* shapes sail.
*
* @param data RDF content to parse
* @param format RDF format to use
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(String data, RDFFormat format, Sail shapesRepo) {
Objects.requireNonNull(data, "data");
Objects.requireNonNull(format, "rdfFormat");
return validateLoadedData("string content", connection -> connection.add(new StringReader(data), format),
shapesRepo);
}
/**
* Load data from a file using an auto-detected RDF format and validate it against the supplied shapes sail.
*
* @param dataFile data file to parse
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(File dataFile, Sail shapesRepo) {
Objects.requireNonNull(dataFile, "dataFile");
RDFFormat format = detectRdfFormat("file " + dataFile, dataFile.getName());
return validate(dataFile, format, shapesRepo);
}
/**
* Load data from a path using an auto-detected RDF format and validate it against the supplied shapes sail.
*
* @param dataPath data path to parse
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(Path dataPath, Sail shapesRepo) {
Objects.requireNonNull(dataPath, "dataPath");
RDFFormat format = detectRdfFormat("path " + dataPath, dataPath.getFileName().toString());
return validate(dataPath, format, shapesRepo);
}
/**
* Load data from a URL using an auto-detected RDF format and validate it against the supplied shapes sail.
*
* @param dataUrl data URL to parse
* @param shapesRepo shapes sail
* @return validation report
*/
public ValidationReport validate(URL dataUrl, Sail shapesRepo) {
Objects.requireNonNull(dataUrl, "dataUrl");
RDFFormat format = detectRdfFormat("URL " + dataUrl, dataUrl.getPath());
return validate(dataUrl, format, shapesRepo);
}
/**
* Load shapes from a file and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesFile shapes file to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, File shapesFile, String baseURI, RDFFormat format) {
return builder.withShapes(shapesFile, baseURI, format).build().validate(dataRepo);
}
/**
* Load shapes from a path and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesPath shapes path to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, Path shapesPath, String baseURI, RDFFormat format) {
return builder.withShapes(shapesPath, baseURI, format).build().validate(dataRepo);
}
/**
* Load shapes from a URL and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesUrl shapes URL to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, URL shapesUrl, String baseURI, RDFFormat format) {
return builder.withShapes(shapesUrl, baseURI, format).build().validate(dataRepo);
}
/**
* Load shapes from an input stream and validate the supplied data sail. The input stream is not closed by this
* method.
*
* @param dataRepo data sail to validate
* @param shapesInputStream shapes input stream to parse
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, InputStream shapesInputStream, String baseURI,
RDFFormat format) {
return builder.withShapes(shapesInputStream, baseURI, format).build().validate(dataRepo);
}
/**
* Load shapes from RDF content in a string and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapes RDF content for shapes
* @param baseURI base URI for resolving relative IRIs
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, String shapes, String baseURI, RDFFormat format) {
return builder.withShapes(shapes, baseURI, format).build().validate(dataRepo);
}
/**
* Load shapes from a file using an auto-detected RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesFile shapes file to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, File shapesFile, String baseURI) {
return builder.withShapes(shapesFile, baseURI).build().validate(dataRepo);
}
/**
* Load shapes from a path using an auto-detected RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesPath shapes path to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, Path shapesPath, String baseURI) {
return builder.withShapes(shapesPath, baseURI).build().validate(dataRepo);
}
/**
* Load shapes from a URL using an auto-detected RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesUrl shapes URL to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, URL shapesUrl, String baseURI) {
return builder.withShapes(shapesUrl, baseURI).build().validate(dataRepo);
}
/**
* Load shapes from an input stream using an auto-detected RDF format and validate the supplied data sail. The
* input stream is not closed by this method.
*
* @param dataRepo data sail to validate
* @param shapesInputStream shapes input stream to parse
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, InputStream shapesInputStream, String baseURI) {
return builder.withShapes(shapesInputStream, baseURI).build().validate(dataRepo);
}
/**
* Load shapes from RDF content in a string using an auto-detected RDF format and validate the supplied data
* sail.
*
* @param dataRepo data sail to validate
* @param shapes RDF content for shapes
* @param baseURI base URI for resolving relative IRIs
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, String shapes, String baseURI) {
return builder.withShapes(shapes, baseURI).build().validate(dataRepo);
}
/**
* Load shapes from a file using the supplied RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesFile shapes file to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, File shapesFile, RDFFormat format) {
return builder.withShapes(shapesFile, format).build().validate(dataRepo);
}
/**
* Load shapes from a path using the supplied RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesPath shapes path to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, Path shapesPath, RDFFormat format) {
return builder.withShapes(shapesPath, format).build().validate(dataRepo);
}
/**
* Load shapes from a URL using the supplied RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesUrl shapes URL to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, URL shapesUrl, RDFFormat format) {
return builder.withShapes(shapesUrl, format).build().validate(dataRepo);
}
/**
* Load shapes from an input stream using the supplied RDF format and validate the supplied data sail. The input
* stream is not closed by this method.
*
* @param dataRepo data sail to validate
* @param shapesInputStream shapes input stream to parse
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, InputStream shapesInputStream, RDFFormat format) {
return builder.withShapes(shapesInputStream, format).build().validate(dataRepo);
}
/**
* Load shapes from RDF content in a string using the supplied RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapes RDF content for shapes
* @param format RDF format to use
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, String shapes, RDFFormat format) {
return builder.withShapes(shapes, format).build().validate(dataRepo);
}
/**
* Load shapes from a file using an auto-detected RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesFile shapes file to parse
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, File shapesFile) {
return builder.withShapes(shapesFile).build().validate(dataRepo);
}
/**
* Load shapes from a path using an auto-detected RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesPath shapes path to parse
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, Path shapesPath) {
return builder.withShapes(shapesPath).build().validate(dataRepo);
}
/**
* Load shapes from a URL using an auto-detected RDF format and validate the supplied data sail.
*
* @param dataRepo data sail to validate
* @param shapesUrl shapes URL to parse
* @return validation report
*/
public ValidationReport validate(Sail dataRepo, URL shapesUrl) {
return builder.withShapes(shapesUrl).build().validate(dataRepo);
}
}
private static ValidationReport validateInternal(Sail dataRepo, Sail shapesRepo, InternalBuilder<?> settings) {
Objects.requireNonNull(dataRepo, "dataRepo");
Objects.requireNonNull(shapesRepo, "shapesRepo");
Objects.requireNonNull(settings, "settings");
if (!settings.validationEnabled) {
return new ValidationReport(true);
}
if (settings.validationTimeoutMillis >= 0) {
return validateInternalWithTimeout(dataRepo, shapesRepo, settings);
}
return validateInternalWithoutTimeout(dataRepo, shapesRepo, settings);
}
private static ValidationReport validateInternalWithTimeout(Sail dataRepo, Sail shapesRepo,
InternalBuilder<?> settings) {
long validationTimeoutMillis = settings.validationTimeoutMillis;
ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> {
Thread thread = new Thread(runnable, "ShaclValidator-timeout");
thread.setDaemon(true);
return thread;
});
Future<ValidationReport> future = null;
try {
future = executorService.submit(() -> {
ValidationReport report = validateInternalWithoutTimeout(dataRepo, shapesRepo, settings);
// Fully evaluate the report so the timeout covers the entire validation process.
report.conforms();
return report;
});
return future.get(validationTimeoutMillis, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
future.cancel(true);
throw new SailException(
"SHACL validation timed out after " + validationTimeoutMillis + " ms", e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new InterruptedSailException(e);
} catch (ExecutionException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
throw new SailException(cause);
} finally {
executorService.shutdownNow();
}
}
private static ValidationReport validateInternalWithoutTimeout(Sail dataRepo, Sail shapesRepo,
InternalBuilder<?> settings) {
List<ContextWithShape> shapes = readShapes(shapesRepo, settings);
if (shapes.isEmpty()) {
return new ValidationReport(true);
}
try (SailConnection dataRepoConnection = dataRepo.getConnection()) {
dataRepoConnection.begin(IsolationLevels.NONE);
try {
RdfsSubClassOfReasoner reasoner;
SailConnection baseConnection = dataRepoConnection;
ConnectionsGroup.RdfsSubClassOfReasonerProvider rdfsSubClassOfReasonerProvider = null;
if (settings.rdfsSubClassReasoning) {
try (SailConnection shapesConnection = shapesRepo.getConnection()) {
shapesConnection.begin(IsolationLevels.NONE);
try {
reasoner = RdfsSubClassOfReasoner.createReasoner(dataRepoConnection, shapesConnection,
new ValidationSettings(ALL_CONTEXTS, settings.logValidationPlans, true,
settings.performanceLogging));
} finally {
shapesConnection.commit();
}
}
RdfsSubClassOfReasoner finalReasoner = reasoner;
rdfsSubClassOfReasonerProvider = () -> finalReasoner;
baseConnection = new VerySimpleRdfsBackwardsChainingConnection(dataRepoConnection, finalReasoner);
}
ShaclSailConnection.Settings transactionSettings = new ShaclSailConnection.Settings(
settings.cacheSelectNodes,
settings.validationEnabled,
settings.parallelValidation,
IsolationLevels.NONE);
try (ConnectionsGroup connectionsGroup = new ConnectionsGroup(
baseConnection,
null,
null,
null,
new Stats(),
rdfsSubClassOfReasonerProvider,
transactionSettings,
settings.sparqlValidation)) {
return performValidation(shapes, connectionsGroup, settings);
}
} finally {
dataRepoConnection.commit();
}
}
}
private static List<ContextWithShape> readShapes(Sail shapesRepo, InternalBuilder<?> settings) {
Resource[] shapeContexts = settings.shapeContexts == null ? ALL_CONTEXTS : settings.shapeContexts;
Shape.ParseSettings parseSettings = new Shape.ParseSettings(settings.eclipseRdf4jShaclExtensions,
settings.dashDataShapes);
try (SailConnection shapesConnection = shapesRepo.getConnection()) {
shapesConnection.begin(IsolationLevels.NONE);
try (ShapeSource rootShapeSource = new CombinedShapeSource(shapesConnection, shapesConnection)) {
ShapeSource configuredShapeSource = rootShapeSource.withContext(shapeContexts);
List<ContextWithShape> shapes = Shape.Factory.getShapes(configuredShapeSource, parseSettings);
if (!shapes.isEmpty()) {
return shapes;
}
boolean hasMappings = shapesConnection.hasStatement(null, SHACL.SHAPES_GRAPH, null, false,
shapeContexts)
|| shapesConnection.hasStatement(null, RDF.TYPE, RSX.DataAndShapesGraphLink, false,
shapeContexts);
if (hasMappings) {
return shapes;
}
LinkedHashSet<Resource> fallbackShapesGraphs = new LinkedHashSet<>();
if (settings.shapeContexts == null) {
fallbackShapesGraphs.add(null);
try (var contextIds = shapesConnection.getContextIDs()) {
while (contextIds.hasNext()) {
fallbackShapesGraphs.add(contextIds.next());
}
}
} else {
fallbackShapesGraphs.addAll(Arrays.asList(shapeContexts));
}
Cache cache = new Cache();
List<ContextWithShape> parsed = new ArrayList<>();
for (Resource shapesGraph : fallbackShapesGraphs) {
parsed.addAll(Shape.Factory.getShapesInContext(rootShapeSource, parseSettings, cache, ALL_CONTEXTS,
new Resource[] { shapesGraph }));
}
return Shape.Factory.getShapes(parsed);
} finally {
shapesConnection.commit();
}
}
}
private static ValidationReport performValidation(List<ContextWithShape> shapes, ConnectionsGroup connectionsGroup,
InternalBuilder<?> settings) {
long effectiveValidationResultsLimitPerConstraint = settings.getEffectiveValidationResultsLimitPerConstraint();
long validationResultsLimitTotal = settings.validationResultsLimitTotal;
List<ValidationResultIterator> validationResultIterators = shapes
.stream()
.map(contextWithShape -> new ShapeValidationContainer(
contextWithShape.getShape(),
() -> contextWithShape.getShape()
.generatePlans(connectionsGroup,
new ValidationSettings(contextWithShape.getDataGraph(),
settings.logValidationPlans,
true, settings.performanceLogging)),
settings.globalLogValidationExecution,
settings.logValidationViolations,
effectiveValidationResultsLimitPerConstraint,
settings.performanceLogging,
settings.logValidationPlans,
logger,
connectionsGroup)
)
.filter(ShapeValidationContainer::hasPlanNode)
.map(ShapeValidationContainer::performValidation)
.collect(Collectors.toList());
if (Thread.currentThread().isInterrupted()) {
Thread.currentThread().interrupt();
throw new InterruptedSailException("Thread was interrupted during validation.");
}
return new LazyValidationReport(validationResultIterators, validationResultsLimitTotal);
}
}