ShaclValidatorFluentApiTest.java
/*******************************************************************************
* Copyright (c) 2025 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 static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Resource;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
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.sail.Sail;
import org.eclipse.rdf4j.sail.SailConnection;
import org.eclipse.rdf4j.sail.SailException;
import org.eclipse.rdf4j.sail.helpers.AbstractSail;
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.eclipse.rdf4j.sail.shacl.results.ValidationReport;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.io.TempDir;
class ShaclValidatorFluentApiTest {
@Test
void builderExposesShaclSailConfigurationMethods() {
assertAll(
() -> assertHasMethod(ShaclValidator.Builder.class, "setGlobalLogValidationExecution", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setLogValidationViolations", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setParallelValidation", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setCacheSelectNodes", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setRdfsSubClassReasoning", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "disableValidation"),
() -> assertHasMethod(ShaclValidator.Builder.class, "enableValidation"),
() -> assertHasMethod(ShaclValidator.Builder.class, "setLogValidationPlans", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setPerformanceLogging", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setSerializableValidation", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setEclipseRdf4jShaclExtensions", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setDashDataShapes", boolean.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setValidationResultsLimitPerConstraint",
long.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setValidationResultsLimitTotal", long.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setTransactionalValidationLimit", long.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setValidationTimeoutMillis", long.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "setShapesGraphs", Set.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", File.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", Path.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", URL.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", InputStream.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", String.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", File.class, String.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", Path.class, String.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", URL.class, String.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", InputStream.class, String.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", String.class, String.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", File.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", Path.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", URL.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", InputStream.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", File.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", Path.class),
() -> assertHasMethod(ShaclValidator.Builder.class, "withShapes", URL.class));
}
@Test
void validatorExposesShapeSourceValidationOverloads() {
assertAll(
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, File.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, Path.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, URL.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, InputStream.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, String.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, File.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, Path.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, URL.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, InputStream.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, File.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, Path.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Sail.class, URL.class));
}
@Test
void validatorExposesDataSourceValidationOverloads() {
assertAll(
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", File.class, String.class,
RDFFormat.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Path.class, String.class,
RDFFormat.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", URL.class, String.class,
RDFFormat.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", InputStream.class, String.class,
RDFFormat.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", String.class, String.class,
RDFFormat.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", File.class, String.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Path.class, String.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", URL.class, String.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", InputStream.class, String.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", String.class, String.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", File.class, RDFFormat.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Path.class, RDFFormat.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", URL.class, RDFFormat.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", InputStream.class, RDFFormat.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", String.class, RDFFormat.class,
Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", File.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", Path.class, Sail.class),
() -> assertHasMethod(ShaclValidator.Validator.class, "validate", URL.class, Sail.class));
}
@Test
void validatorWithShapesExposesDataSourceValidationOverloads() {
assertAll(
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", File.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", Path.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", URL.class, String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", InputStream.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", String.class,
String.class, RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", File.class, String.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", Path.class, String.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", URL.class, String.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", InputStream.class,
String.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", String.class,
String.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", File.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", Path.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", URL.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", InputStream.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", String.class,
RDFFormat.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", File.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", Path.class),
() -> assertHasMethod(ShaclValidator.ValidatorWithShapes.class, "validate", URL.class));
}
@Test
void builderWithShapesFromFileLoadsShapesUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapes(shapesPath.toFile(), baseUri, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from File");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromPathLoadsShapesUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapes(shapesPath, baseUri, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from Path");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromUrlLoadsShapesUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapes(shapesPath.toUri().toURL(), baseUri, RDFFormat.TURTLE,
dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from URL");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromInputStreamLoadsShapesUsingBaseUriAndFormat() throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try (InputStream inputStream = new ByteArrayInputStream(shapesTtl.getBytes(StandardCharsets.UTF_8))) {
ValidationReport report = validateWithShapes(inputStream, baseUri, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from InputStream");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromStringLoadsShapesUsingBaseUriAndFormat() throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapes(shapesTtl, baseUri, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from String content");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromFileAutoDetectsFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapesAuto(shapesPath.toFile(), baseUri, dataRepo);
assertFalse(report.conforms(), "expected validation to run when auto-detecting format from File");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromPathAutoDetectsFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapesAuto(shapesPath, baseUri, dataRepo);
assertFalse(report.conforms(), "expected validation to run when auto-detecting format from Path");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromUrlAutoDetectsFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapesAuto(shapesPath.toUri().toURL(), baseUri, dataRepo);
assertFalse(report.conforms(), "expected validation to run when auto-detecting format from URL");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromInputStreamAutoDetectsFormat() throws Exception {
String baseUri = "http://example.com/shapes.ttl";
String shapesTtl = relativeShapesTtl();
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try (InputStream inputStream = new ByteArrayInputStream(shapesTtl.getBytes(StandardCharsets.UTF_8))) {
ValidationReport report = validateWithShapesAuto(inputStream, baseUri, dataRepo);
assertFalse(report.conforms(), "expected validation to run when auto-detecting format from InputStream");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromStringAutoDetectsFormat() throws Exception {
String baseUri = "http://example.com/shapes.ttl";
String shapesTtl = relativeShapesTtl();
SailRepository dataRepo = createDataRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithShapesAuto(shapesTtl, baseUri, dataRepo);
assertFalse(report.conforms(), "expected validation to run when auto-detecting format from String");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromFileWithoutBaseUriLoadsShapes(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesNoBaseUri(shapesPath.toFile(), RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from File without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromPathWithoutBaseUriLoadsShapes(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesNoBaseUri(shapesPath, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from Path without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromUrlWithoutBaseUriLoadsShapes(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesNoBaseUri(shapesPath.toUri().toURL(), RDFFormat.TURTLE,
dataRepo);
assertFalse(report.conforms(), "expected validation to run when loading shapes from URL without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromInputStreamWithoutBaseUriLoadsShapes() throws Exception {
String shapesTtl = absoluteShapesTtl();
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try (InputStream inputStream = new ByteArrayInputStream(shapesTtl.getBytes(StandardCharsets.UTF_8))) {
ValidationReport report = validateWithShapesNoBaseUri(inputStream, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(),
"expected validation to run when loading shapes from InputStream without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromStringWithoutBaseUriLoadsShapes() throws Exception {
String shapesTtl = absoluteShapesTtl();
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesNoBaseUri(shapesTtl, RDFFormat.TURTLE, dataRepo);
assertFalse(report.conforms(),
"expected validation to run when loading shapes from String without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromFileAutoDetectsFormatWithoutBaseUri(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesAutoNoBaseUri(shapesPath.toFile(), dataRepo);
assertFalse(report.conforms(),
"expected validation to run when auto-detecting format from File without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromPathAutoDetectsFormatWithoutBaseUri(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesAutoNoBaseUri(shapesPath, dataRepo);
assertFalse(report.conforms(),
"expected validation to run when auto-detecting format from Path without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void builderWithShapesFromUrlAutoDetectsFormatWithoutBaseUri(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
Path shapesPath = writeShapesFile(tempDir, shapesTtl);
SailRepository dataRepo = createDataRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithShapesAutoNoBaseUri(shapesPath.toUri().toURL(), dataRepo);
assertFalse(report.conforms(),
"expected validation to run when auto-detecting format from URL without base URI");
} finally {
dataRepo.shutDown();
}
}
@Test
void validatorLoadsDataFromFileUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
Path dataPath = writeDataFile(tempDir, relativeDataTtl());
SailRepository shapesRepo = createShapesRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithValidatorData(dataPath.toFile(), baseUri, RDFFormat.TURTLE,
shapesRepo.getSail());
assertFalse(report.conforms(), "expected validator to load data from File");
} finally {
shapesRepo.shutDown();
}
}
@Test
void validatorAutoDetectsDataFormatWithBaseUri(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns/data.ttl";
Path dataPath = writeDataFile(tempDir, relativeDataTtl());
SailRepository shapesRepo = createShapesRepoForBaseUri(baseUri);
try {
ValidationReport report = validateWithValidatorDataAuto(dataPath.toUri().toURL(), baseUri,
shapesRepo.getSail());
assertFalse(report.conforms(), "expected validator to auto-detect data format with base URI");
} finally {
shapesRepo.shutDown();
}
}
@Test
void validatorLoadsDataWithoutBaseUriUsingFormat(@TempDir Path tempDir) throws Exception {
Path dataPath = writeDataFile(tempDir, absoluteDataTtl());
SailRepository shapesRepo = createShapesRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithValidatorDataNoBaseUri(dataPath, RDFFormat.TURTLE,
shapesRepo.getSail());
assertFalse(report.conforms(), "expected validator to load data without base URI");
} finally {
shapesRepo.shutDown();
}
}
@Test
void validatorAutoDetectsDataFormatWithoutBaseUri(@TempDir Path tempDir) throws Exception {
Path dataPath = writeDataFile(tempDir, absoluteDataTtl());
SailRepository shapesRepo = createShapesRepoForAbsoluteShapes();
try {
ValidationReport report = validateWithValidatorDataAutoNoBaseUri(dataPath.toFile(),
shapesRepo.getSail());
assertFalse(report.conforms(), "expected validator to auto-detect data format without base URI");
} finally {
shapesRepo.shutDown();
}
}
@Test
void validatorWithShapesLoadsDataFromFileUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithData(dataPath.toFile(), baseUri, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from File");
}
@Test
void validatorWithShapesLoadsDataFromPathUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithData(dataPath, baseUri, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from Path");
}
@Test
void validatorWithShapesLoadsDataFromUrlUsingBaseUriAndFormat(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithData(dataPath.toUri().toURL(), baseUri, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from URL");
}
@Test
void validatorWithShapesLoadsDataFromInputStreamUsingBaseUriAndFormat() throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
try (InputStream inputStream = new ByteArrayInputStream(dataTtl.getBytes(StandardCharsets.UTF_8))) {
ValidationReport report = validateWithData(inputStream, baseUri, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from InputStream");
}
}
@Test
void validatorWithShapesLoadsDataFromStringUsingBaseUriAndFormat() throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
ValidationReport report = validateWithData(dataTtl, baseUri, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from String");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromFileWithBaseUri(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataAuto(dataPath.toFile(), baseUri, shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from File");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromPathWithBaseUri(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataAuto(dataPath, baseUri, shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from Path");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromUrlWithBaseUri(@TempDir Path tempDir) throws Exception {
String baseUri = "http://example.com/ns";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataAuto(dataPath.toUri().toURL(), baseUri, shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from URL");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromInputStreamWithBaseUri() throws Exception {
String baseUri = "http://example.com/data.ttl";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
try (InputStream inputStream = new ByteArrayInputStream(dataTtl.getBytes(StandardCharsets.UTF_8))) {
ValidationReport report = validateWithDataAuto(inputStream, baseUri, shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from InputStream");
}
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromStringWithBaseUri() throws Exception {
String baseUri = "http://example.com/data.ttl";
String shapesTtl = relativeShapesTtl();
String dataTtl = relativeDataTtl();
ValidationReport report = validateWithDataAuto(dataTtl, baseUri, shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from String");
}
@Test
void validatorWithShapesLoadsDataFromFileWithoutBaseUriUsingFormat(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataNoBaseUri(dataPath.toFile(), RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from File without base URI");
}
@Test
void validatorWithShapesLoadsDataFromPathWithoutBaseUriUsingFormat(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataNoBaseUri(dataPath, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from Path without base URI");
}
@Test
void validatorWithShapesLoadsDataFromUrlWithoutBaseUriUsingFormat(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataNoBaseUri(dataPath.toUri().toURL(), RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from URL without base URI");
}
@Test
void validatorWithShapesLoadsDataFromInputStreamWithoutBaseUriUsingFormat() throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
try (InputStream inputStream = new ByteArrayInputStream(dataTtl.getBytes(StandardCharsets.UTF_8))) {
ValidationReport report = validateWithDataNoBaseUri(inputStream, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(),
"expected validation to run when loading data from InputStream without base URI");
}
}
@Test
void validatorWithShapesLoadsDataFromStringWithoutBaseUriUsingFormat() throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
ValidationReport report = validateWithDataNoBaseUri(dataTtl, RDFFormat.TURTLE, shapesTtl);
assertFalse(report.conforms(), "expected validation to run when loading data from String without base URI");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromFileWithoutBaseUri(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataAutoNoBaseUri(dataPath.toFile(), shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from File without base URI");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromPathWithoutBaseUri(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataAutoNoBaseUri(dataPath, shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from Path without base URI");
}
@Test
void validatorWithShapesAutoDetectsDataFormatFromUrlWithoutBaseUri(@TempDir Path tempDir) throws Exception {
String shapesTtl = absoluteShapesTtl();
String dataTtl = absoluteDataTtl();
Path dataPath = writeDataFile(tempDir, dataTtl);
ValidationReport report = validateWithDataAutoNoBaseUri(dataPath.toUri().toURL(), shapesTtl);
assertFalse(report.conforms(), "expected validation to auto-detect data format from URL without base URI");
}
@Test
void validatorUsesDefensiveCopyOfShapesGraphsSet() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
IRI graph1 = iri("http://example.com/graph1");
IRI graph2 = iri("http://example.com/graph2");
addShape(shapesRepo, graph1, "http://example.com/ns#p1");
addShape(shapesRepo, graph2, "http://example.com/ns#p2");
SailRepository dataRepo = new SailRepository(new MemoryStore());
addData(dataRepo, "http://example.com/ns#a", "http://example.com/ns#p1");
Set<IRI> shapesGraphs = new HashSet<>();
shapesGraphs.add(graph1);
ShaclValidator.Builder builder = ShaclValidator.builder();
invoke(builder, "setShapesGraphs", shapesGraphs);
ShaclValidator.Validator validator = builder.build();
shapesGraphs.clear();
shapesGraphs.add(graph2);
ValidationReport report = validator.validate(dataRepo.getSail(), shapesRepo.getSail());
assertTrue(report.conforms(), "validator should not be affected by mutations to the original Set");
}
@Test
void setShapesGraphsTreatsRdf4jNilAsDefaultGraphForMappings() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
IRI shapesGraph = iri("http://example.com/ns#shapesGraph");
String shapesTtl = "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
"\n" +
"ex:PersonShape a sh:NodeShape ;\n" +
" sh:targetClass ex:Person ;\n" +
" sh:property [\n" +
" sh:path ex:required ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
try (SailRepositoryConnection conn = shapesRepo.getConnection()) {
conn.add(RDF4J.NIL, SHACL.SHAPES_GRAPH, shapesGraph);
conn.add(new StringReader(shapesTtl), "", RDFFormat.TURTLE, shapesGraph);
}
SailRepository dataRepo = new SailRepository(new MemoryStore());
try (SailRepositoryConnection conn = dataRepo.getConnection()) {
conn.add(iri("http://example.com/ns#alice"), RDF.TYPE, iri("http://example.com/ns#Person"));
}
ShaclValidator.Builder builder = ShaclValidator.builder();
invoke(builder, "setShapesGraphs", Set.of(RDF4J.NIL));
ValidationReport report = builder.build().validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(report.conforms(), "expected default-graph mapping statements to be honored via rdf4j:nil");
}
@Test
void setShapesGraphToNullShouldDiscoverAllShapes() throws Exception {
IRI[] contexts = { null, RDF4J.SHACL_SHAPE_GRAPH, RDF4J.NIL, iri("http://example.com/otherGraph") };
for (IRI context : contexts) {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
String shapesTtl = "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
"\n" +
"ex:PersonShape a sh:NodeShape ;\n" +
" sh:targetClass ex:Person ;\n" +
" sh:property [\n" +
" sh:path ex:required ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
try (SailRepositoryConnection conn = shapesRepo.getConnection()) {
if (context == null) {
conn.add(new StringReader(shapesTtl), "", RDFFormat.TURTLE);
} else {
conn.add(new StringReader(shapesTtl), "", RDFFormat.TURTLE, context);
}
}
SailRepository dataRepo = new SailRepository(new MemoryStore());
try (SailRepositoryConnection conn = dataRepo.getConnection()) {
conn.add(iri("http://example.com/ns#alice"), RDF.TYPE, iri("http://example.com/ns#Person"));
}
ShaclValidator.Builder builder = ShaclValidator.builder();
builder.setShapesGraphs(null);
ValidationReport report = builder.build().validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(report.conforms(), "expected all shapes to be discovered when null is provided");
shapesRepo.shutDown();
}
}
@Test
void validatorUsesDefensiveCopyOfShapeContextsArray() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
IRI graph1 = iri("http://example.com/graph1");
IRI graph2 = iri("http://example.com/graph2");
addShape(shapesRepo, graph1, "http://example.com/ns#p1");
addShape(shapesRepo, graph2, "http://example.com/ns#p2");
SailRepository dataRepo = new SailRepository(new MemoryStore());
addData(dataRepo, "http://example.com/ns#a", "http://example.com/ns#p1");
IRI[] contexts = new IRI[] { graph1 };
ShaclValidator.Builder builder = ShaclValidator.builder().shapeContexts(contexts);
ShaclValidator.Validator validator = builder.build();
contexts[0] = graph2;
ValidationReport report = validator.validate(dataRepo.getSail(), shapesRepo.getSail());
assertTrue(report.conforms(), "validator should not be affected by mutations to the original array");
}
@Test
void validatorDefaultsToReadingAllShapeContexts() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
addManyViolationsShape(shapesRepo, RDF4J.SHACL_SHAPE_GRAPH);
SailRepository dataRepo = new SailRepository(new MemoryStore());
addManyViolationsData(dataRepo, 1);
ValidationReport report = ShaclValidator.builder()
.build()
.validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(report.conforms(), "expected validation to run against all shapes by default");
}
@Test
void builderDefaultsToIncludingDefaultGraphShapes() throws Exception {
IRI[] contexts = { null, RDF4J.SHACL_SHAPE_GRAPH, RDF4J.NIL, iri("http://example.com/otherGraph") };
String ttl = "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
"\n" +
"ex:Shape a sh:NodeShape ;\n" +
" sh:targetClass ex:Person ;\n" +
" sh:property [\n" +
" sh:path ex:required ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
for (IRI context : contexts) {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
try (SailRepositoryConnection conn = shapesRepo.getConnection()) {
if (context == null) {
conn.add(new StringReader(ttl), "", RDFFormat.TURTLE);
} else {
conn.add(new StringReader(ttl), "", RDFFormat.TURTLE, context);
}
}
SailRepository dataRepo = new SailRepository(new MemoryStore());
addManyViolationsData(dataRepo, 1);
ValidationReport report = ShaclValidator.builder()
.build()
.validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(report.conforms(),
"builder validation should include all graphs by default, failed for context: " + context);
shapesRepo.shutDown();
}
}
@Test
void settingsAreCopiedFromBuilderToBuilderWithShapesToValidatorWithShapes() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
IRI graph1 = iri("http://example.com/graph1");
IRI graph2 = iri("http://example.com/graph2");
addShape(shapesRepo, graph1, "http://example.com/ns#p1");
addShape(shapesRepo, graph2, "http://example.com/ns#p2");
SailRepository dataRepo = new SailRepository(new MemoryStore());
addData(dataRepo, "http://example.com/ns#a", "http://example.com/ns#p1");
ShaclValidator.Builder builder = ShaclValidator.builder();
invoke(builder, "setShapesGraphs", Set.of(graph1));
ShaclValidator.BuilderWithShapes builderWithShapes = builder.withShapes(shapesRepo.getSail());
invoke(builder, "setShapesGraphs", Set.of(graph2));
ShaclValidator.ValidatorWithShapes validatorWithShapes = builderWithShapes.build();
ValidationReport report = validatorWithShapes.validate(dataRepo.getSail());
assertTrue(report.conforms(), "builder changes should not affect an already created BuilderWithShapes");
}
@Test
void settingsFromShaclSailAreCopied() throws Exception {
ShaclSail shaclSail = new ShaclSail(new MemoryStore());
IRI graph = iri("http://example.com/graph1");
shaclSail.setShapesGraphs(Set.of(graph));
shaclSail.setValidationResultsLimitTotal(1);
ShaclValidator.Builder builder = ShaclValidator.Builder.settingsFrom(shaclSail);
SailRepository shapesRepo = new SailRepository(new MemoryStore());
addManyViolationsShape(shapesRepo, graph);
SailRepository dataRepo = new SailRepository(new MemoryStore());
addManyViolationsData(dataRepo, 3);
ValidationReport report = builder.build().validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(report.conforms());
assertTrue(report.isTruncated());
assertEquals(1, report.getValidationResult().size());
}
@Test
void rdfsSubClassReasoningSettingAffectsTargetClass() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
IRI shapesGraph = RDF4J.SHACL_SHAPE_GRAPH;
addTargetClassShape(shapesRepo, shapesGraph);
SailRepository dataRepo = new SailRepository(new MemoryStore());
addSubClassData(dataRepo);
ShaclValidator.Builder builder = ShaclValidator.builder();
invoke(builder, "setShapesGraphs", Set.of(shapesGraph));
invoke(builder, "setRdfsSubClassReasoning", false);
ValidationReport reportWithoutReasoning = builder.build().validate(dataRepo.getSail(), shapesRepo.getSail());
assertTrue(reportWithoutReasoning.conforms(), "without RDFS reasoning, the targetClass should not match");
invoke(builder, "setRdfsSubClassReasoning", true);
ValidationReport reportWithReasoning = builder.build().validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(reportWithReasoning.conforms(), "with RDFS reasoning, the targetClass should match via subclass");
}
@Test
void validationResultsLimitsAreApplied() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
IRI shapesGraph = RDF4J.SHACL_SHAPE_GRAPH;
addManyViolationsShape(shapesRepo, shapesGraph);
SailRepository dataRepo = new SailRepository(new MemoryStore());
addManyViolationsData(dataRepo, 3);
ShaclValidator.Builder builder = ShaclValidator.builder();
invoke(builder, "setShapesGraphs", Set.of(shapesGraph));
invoke(builder, "setValidationResultsLimitTotal", 100L);
invoke(builder, "setValidationResultsLimitPerConstraint", 1L);
ValidationReport report = builder.build().validate(dataRepo.getSail(), shapesRepo.getSail());
assertFalse(report.conforms());
assertTrue(report.isTruncated());
assertEquals(1, report.getValidationResult().size());
}
@Test
void fromShaclSailUsesItsShapesAndCopiedSettings() throws Exception {
ShaclSail shaclSail = new ShaclSail(new MemoryStore());
shaclSail.setValidationResultsLimitTotal(1);
SailRepository shaclRepository = new SailRepository(shaclSail);
try {
addManyViolationsShape(shaclRepository, RDF4J.SHACL_SHAPE_GRAPH);
SailRepository dataRepo = new SailRepository(new MemoryStore());
addManyViolationsData(dataRepo, 3);
ValidationReport report = ShaclValidator.from(shaclSail)
.build()
.validate(dataRepo.getSail());
assertFalse(report.conforms());
assertTrue(report.isTruncated());
assertEquals(1, report.getValidationResult().size());
} finally {
shaclRepository.shutDown();
}
}
@Test
@Timeout(5)
void validationTimeoutMillisAbortsValidation() throws Exception {
ShaclValidator.Builder builder = ShaclValidator.builder();
invoke(builder, "setValidationTimeoutMillis", 50L);
Sail dataRepo = new MemoryStore();
Sail shapesRepo = new BlockingSail();
SailException exception = assertThrows(SailException.class,
() -> builder.build().validate(dataRepo, shapesRepo));
assertTrue(exception.getMessage().contains("timed out"), "Expected a validation-timeout error");
}
@Test
void allSettingsAreCopiedFromBuilderToValidatorAndUnaffectedByLaterMutations() throws Exception {
IRI graph1 = iri("http://example.com/graph1");
IRI graph2 = iri("http://example.com/graph2");
ShaclValidator.Builder builder = ShaclValidator.builder()
.shapeContexts(graph1, graph2)
.setParallelValidation(false)
.setLogValidationPlans(true)
.setLogValidationViolations(true)
.setGlobalLogValidationExecution(true)
.setCacheSelectNodes(false)
.setRdfsSubClassReasoning(false)
.setPerformanceLogging(true)
.setSerializableValidation(false)
.setEclipseRdf4jShaclExtensions(true)
.setDashDataShapes(true)
.setValidationResultsLimitTotal(123L)
.setValidationResultsLimitPerConstraint(45L)
.setTransactionalValidationLimit(67L)
.setValidationTimeoutMillis(89L)
.disableValidation();
ShaclValidator.Validator validator = builder.build();
// mutate builder after creating the validator
builder.shapeContexts(RDF4J.SHACL_SHAPE_GRAPH)
.setParallelValidation(true)
.setLogValidationPlans(false)
.setLogValidationViolations(false)
.setGlobalLogValidationExecution(false)
.setCacheSelectNodes(true)
.setRdfsSubClassReasoning(true)
.setPerformanceLogging(false)
.setSerializableValidation(true)
.setEclipseRdf4jShaclExtensions(false)
.setDashDataShapes(false)
.setValidationResultsLimitTotal(999L)
.setValidationResultsLimitPerConstraint(999L)
.setTransactionalValidationLimit(999L)
.setValidationTimeoutMillis(999L)
.enableValidation();
Object validatorBuilder = getFieldValue(validator, "builder");
assertArrayEquals(new Resource[] { graph1, graph2 },
(Resource[]) getFieldValue(validatorBuilder, "shapeContexts"));
assertEquals(false, getFieldValue(validatorBuilder, "parallelValidation"));
assertEquals(true, getFieldValue(validatorBuilder, "logValidationPlans"));
assertEquals(true, getFieldValue(validatorBuilder, "logValidationViolations"));
assertEquals(false, getFieldValue(validatorBuilder, "validationEnabled"));
assertEquals(false, getFieldValue(validatorBuilder, "cacheSelectNodes"));
assertEquals(true, getFieldValue(validatorBuilder, "globalLogValidationExecution"));
assertEquals(false, getFieldValue(validatorBuilder, "rdfsSubClassReasoning"));
assertEquals(true, getFieldValue(validatorBuilder, "performanceLogging"));
assertEquals(false, getFieldValue(validatorBuilder, "serializableValidation"));
assertEquals(true, getFieldValue(validatorBuilder, "eclipseRdf4jShaclExtensions"));
assertEquals(true, getFieldValue(validatorBuilder, "dashDataShapes"));
assertEquals(123L, getFieldValue(validatorBuilder, "validationResultsLimitTotal"));
assertEquals(45L, getFieldValue(validatorBuilder, "validationResultsLimitPerConstraint"));
assertEquals(67L, getFieldValue(validatorBuilder, "transactionalValidationLimit"));
assertEquals(89L, getFieldValue(validatorBuilder, "validationTimeoutMillis"));
}
@Test
void allSettingsAreCopiedFromBuilderToBuilderWithShapesToValidatorWithShapesAndUnaffectedByLaterMutations()
throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
Sail shapesSail = shapesRepo.getSail();
IRI graph1 = iri("http://example.com/graph1");
IRI graph2 = iri("http://example.com/graph2");
ShaclValidator.Builder builder = ShaclValidator.builder()
.shapeContexts(graph1, graph2)
.setParallelValidation(false)
.setLogValidationPlans(true)
.setLogValidationViolations(true)
.setGlobalLogValidationExecution(true)
.setCacheSelectNodes(false)
.setRdfsSubClassReasoning(false)
.setPerformanceLogging(true)
.setSerializableValidation(false)
.setEclipseRdf4jShaclExtensions(true)
.setDashDataShapes(true)
.setValidationResultsLimitTotal(123L)
.setValidationResultsLimitPerConstraint(45L)
.setTransactionalValidationLimit(67L)
.setValidationTimeoutMillis(89L)
.disableValidation();
ShaclValidator.BuilderWithShapes builderWithShapes = builder.withShapes(shapesSail);
// mutate builder after creating BuilderWithShapes
builder.shapeContexts(RDF4J.SHACL_SHAPE_GRAPH)
.setParallelValidation(true)
.setLogValidationPlans(false)
.setLogValidationViolations(false)
.setGlobalLogValidationExecution(false)
.setCacheSelectNodes(true)
.setRdfsSubClassReasoning(true)
.setPerformanceLogging(false)
.setSerializableValidation(true)
.setEclipseRdf4jShaclExtensions(false)
.setDashDataShapes(false)
.setValidationResultsLimitTotal(999L)
.setValidationResultsLimitPerConstraint(999L)
.setTransactionalValidationLimit(999L)
.setValidationTimeoutMillis(999L)
.enableValidation();
ShaclValidator.ValidatorWithShapes validatorWithShapes = builderWithShapes.build();
// mutate BuilderWithShapes after creating ValidatorWithShapes
builderWithShapes.shapeContexts(RDF4J.SHACL_SHAPE_GRAPH)
.setParallelValidation(true)
.setLogValidationPlans(false)
.setLogValidationViolations(false)
.setGlobalLogValidationExecution(false)
.setCacheSelectNodes(true)
.setRdfsSubClassReasoning(true)
.setPerformanceLogging(false)
.setSerializableValidation(true)
.setEclipseRdf4jShaclExtensions(false)
.setDashDataShapes(false)
.setValidationResultsLimitTotal(999L)
.setValidationResultsLimitPerConstraint(999L)
.setTransactionalValidationLimit(999L)
.setValidationTimeoutMillis(999L)
.enableValidation();
builderWithShapes.shapes = new MemoryStore();
Object capturedBuilderWithShapes = getFieldValue(validatorWithShapes, "builderWithShapes");
assertSame(shapesSail, getFieldValue(capturedBuilderWithShapes, "shapes"));
assertArrayEquals(new Resource[] { graph1, graph2 },
(Resource[]) getFieldValue(capturedBuilderWithShapes, "shapeContexts"));
assertEquals(false, getFieldValue(capturedBuilderWithShapes, "parallelValidation"));
assertEquals(true, getFieldValue(capturedBuilderWithShapes, "logValidationPlans"));
assertEquals(true, getFieldValue(capturedBuilderWithShapes, "logValidationViolations"));
assertEquals(false, getFieldValue(capturedBuilderWithShapes, "validationEnabled"));
assertEquals(false, getFieldValue(capturedBuilderWithShapes, "cacheSelectNodes"));
assertEquals(true, getFieldValue(capturedBuilderWithShapes, "globalLogValidationExecution"));
assertEquals(false, getFieldValue(capturedBuilderWithShapes, "rdfsSubClassReasoning"));
assertEquals(true, getFieldValue(capturedBuilderWithShapes, "performanceLogging"));
assertEquals(false, getFieldValue(capturedBuilderWithShapes, "serializableValidation"));
assertEquals(true, getFieldValue(capturedBuilderWithShapes, "eclipseRdf4jShaclExtensions"));
assertEquals(true, getFieldValue(capturedBuilderWithShapes, "dashDataShapes"));
assertEquals(123L, getFieldValue(capturedBuilderWithShapes, "validationResultsLimitTotal"));
assertEquals(45L, getFieldValue(capturedBuilderWithShapes, "validationResultsLimitPerConstraint"));
assertEquals(67L, getFieldValue(capturedBuilderWithShapes, "transactionalValidationLimit"));
assertEquals(89L, getFieldValue(capturedBuilderWithShapes, "validationTimeoutMillis"));
}
@Test
void settingsFromShaclSailCopiesAllConfigurationSettings() throws Exception {
ShaclSail shaclSail = new ShaclSail(new MemoryStore());
IRI graph = iri("http://example.com/graph1");
shaclSail.setShapesGraphs(Set.of(graph));
shaclSail.setParallelValidation(false);
shaclSail.setLogValidationPlans(true);
shaclSail.setLogValidationViolations(true);
shaclSail.setGlobalLogValidationExecution(true);
shaclSail.setCacheSelectNodes(false);
shaclSail.setRdfsSubClassReasoning(false);
shaclSail.setSerializableValidation(false);
shaclSail.setPerformanceLogging(true);
shaclSail.setEclipseRdf4jShaclExtensions(true);
shaclSail.setDashDataShapes(true);
shaclSail.setValidationResultsLimitTotal(123L);
shaclSail.setValidationResultsLimitPerConstraint(45L);
shaclSail.setTransactionalValidationLimit(67L);
shaclSail.disableValidation();
ShaclValidator.Builder builder = ShaclValidator.Builder.settingsFrom(shaclSail);
assertArrayEquals(new Resource[] { graph }, (Resource[]) getFieldValue(builder, "shapeContexts"));
assertEquals(false, getFieldValue(builder, "parallelValidation"));
assertEquals(true, getFieldValue(builder, "logValidationPlans"));
assertEquals(true, getFieldValue(builder, "logValidationViolations"));
assertEquals(false, getFieldValue(builder, "validationEnabled"));
assertEquals(false, getFieldValue(builder, "cacheSelectNodes"));
assertEquals(true, getFieldValue(builder, "globalLogValidationExecution"));
assertEquals(false, getFieldValue(builder, "rdfsSubClassReasoning"));
assertEquals(true, getFieldValue(builder, "performanceLogging"));
assertEquals(false, getFieldValue(builder, "serializableValidation"));
assertEquals(true, getFieldValue(builder, "eclipseRdf4jShaclExtensions"));
assertEquals(true, getFieldValue(builder, "dashDataShapes"));
assertEquals(123L, getFieldValue(builder, "validationResultsLimitTotal"));
assertEquals(45L, getFieldValue(builder, "validationResultsLimitPerConstraint"));
assertEquals(67L, getFieldValue(builder, "transactionalValidationLimit"));
}
private static String relativeShapesTtl() {
return "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"\n" +
"<#PersonShape> a sh:NodeShape ;\n" +
" sh:targetClass <#Person> ;\n" +
" sh:property [\n" +
" sh:path <#required> ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
}
private static String absoluteShapesTtl() {
return "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"\n" +
"<http://example.com/ns#PersonShape> a sh:NodeShape ;\n" +
" sh:targetClass <http://example.com/ns#Person> ;\n" +
" sh:property [\n" +
" sh:path <http://example.com/ns#required> ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
}
private static String relativeDataTtl() {
return "<#alice> a <#Person> .\n";
}
private static String absoluteDataTtl() {
return "<http://example.com/ns#alice> a <http://example.com/ns#Person> .\n";
}
private static Path writeShapesFile(Path tempDir, String shapesTtl) throws Exception {
Path shapesFile = tempDir.resolve("shapes.ttl");
Files.writeString(shapesFile, shapesTtl, StandardCharsets.UTF_8);
return shapesFile;
}
private static Path writeDataFile(Path tempDir, String dataTtl) throws Exception {
Path dataFile = tempDir.resolve("data.ttl");
Files.writeString(dataFile, dataTtl, StandardCharsets.UTF_8);
return dataFile;
}
private static SailRepository createDataRepoForBaseUri(String baseUri) throws Exception {
SailRepository dataRepo = new SailRepository(new MemoryStore());
try (SailRepositoryConnection conn = dataRepo.getConnection()) {
conn.add(iri(baseUri + "#alice"), RDF.TYPE, iri(baseUri + "#Person"));
}
return dataRepo;
}
private static SailRepository createDataRepoForAbsoluteShapes() throws Exception {
return createDataRepoForBaseUri("http://example.com/ns");
}
private static SailRepository createShapesRepoForBaseUri(String baseUri) throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
try (SailRepositoryConnection conn = shapesRepo.getConnection()) {
conn.add(new StringReader(relativeShapesTtl()), baseUri, RDFFormat.TURTLE);
}
return shapesRepo;
}
private static SailRepository createShapesRepoForAbsoluteShapes() throws Exception {
SailRepository shapesRepo = new SailRepository(new MemoryStore());
try (SailRepositoryConnection conn = shapesRepo.getConnection()) {
conn.add(new StringReader(absoluteShapesTtl()), "", RDFFormat.TURTLE);
}
return shapesRepo;
}
private static ValidationReport validateWithShapes(Object shapesSource, String baseUri, RDFFormat format,
SailRepository dataRepo) throws Exception {
ShaclValidator.Builder builder = ShaclValidator.builder();
ShaclValidator.BuilderWithShapes builderWithShapes = (ShaclValidator.BuilderWithShapes) invoke(builder,
"withShapes", shapesSource, baseUri, format);
return builderWithShapes.build().validate(dataRepo.getSail());
}
private static ValidationReport validateWithShapesNoBaseUri(Object shapesSource, RDFFormat format,
SailRepository dataRepo) throws Exception {
ShaclValidator.Builder builder = ShaclValidator.builder();
ShaclValidator.BuilderWithShapes builderWithShapes = (ShaclValidator.BuilderWithShapes) invoke(builder,
"withShapes", shapesSource, format);
return builderWithShapes.build().validate(dataRepo.getSail());
}
private static ValidationReport validateWithShapesAuto(Object shapesSource, String baseUri,
SailRepository dataRepo) throws Exception {
ShaclValidator.Builder builder = ShaclValidator.builder();
ShaclValidator.BuilderWithShapes builderWithShapes = (ShaclValidator.BuilderWithShapes) invoke(builder,
"withShapes", shapesSource, baseUri);
return builderWithShapes.build().validate(dataRepo.getSail());
}
private static ValidationReport validateWithShapesAutoNoBaseUri(Object shapesSource,
SailRepository dataRepo) throws Exception {
ShaclValidator.Builder builder = ShaclValidator.builder();
ShaclValidator.BuilderWithShapes builderWithShapes = (ShaclValidator.BuilderWithShapes) invoke(builder,
"withShapes", shapesSource);
return builderWithShapes.build().validate(dataRepo.getSail());
}
private static ValidationReport validateWithData(Object dataSource, String baseUri, RDFFormat format,
String shapesTtl) throws Exception {
ShaclValidator.ValidatorWithShapes validator = ShaclValidator.builder()
.withShapes(shapesTtl, baseUri, RDFFormat.TURTLE)
.build();
return (ValidationReport) invoke(validator, "validate", dataSource, baseUri, format);
}
private static ValidationReport validateWithDataAuto(Object dataSource, String baseUri, String shapesTtl)
throws Exception {
ShaclValidator.ValidatorWithShapes validator = ShaclValidator.builder()
.withShapes(shapesTtl, baseUri, RDFFormat.TURTLE)
.build();
return (ValidationReport) invoke(validator, "validate", dataSource, baseUri);
}
private static ValidationReport validateWithDataNoBaseUri(Object dataSource, RDFFormat format,
String shapesTtl) throws Exception {
ShaclValidator.ValidatorWithShapes validator = ShaclValidator.builder()
.withShapes(shapesTtl, RDFFormat.TURTLE)
.build();
return (ValidationReport) invoke(validator, "validate", dataSource, format);
}
private static ValidationReport validateWithDataAutoNoBaseUri(Object dataSource, String shapesTtl)
throws Exception {
ShaclValidator.ValidatorWithShapes validator = ShaclValidator.builder()
.withShapes(shapesTtl, RDFFormat.TURTLE)
.build();
return (ValidationReport) invoke(validator, "validate", dataSource);
}
private static ValidationReport validateWithValidatorData(Object dataSource, String baseUri, RDFFormat format,
Sail shapesSail) throws Exception {
ShaclValidator.Validator validator = ShaclValidator.builder().build();
return (ValidationReport) invoke(validator, "validate", dataSource, baseUri, format, shapesSail);
}
private static ValidationReport validateWithValidatorDataAuto(Object dataSource, String baseUri, Sail shapesSail)
throws Exception {
ShaclValidator.Validator validator = ShaclValidator.builder().build();
return (ValidationReport) invoke(validator, "validate", dataSource, baseUri, shapesSail);
}
private static ValidationReport validateWithValidatorDataNoBaseUri(Object dataSource, RDFFormat format,
Sail shapesSail) throws Exception {
ShaclValidator.Validator validator = ShaclValidator.builder().build();
return (ValidationReport) invoke(validator, "validate", dataSource, format, shapesSail);
}
private static ValidationReport validateWithValidatorDataAutoNoBaseUri(Object dataSource, Sail shapesSail)
throws Exception {
ShaclValidator.Validator validator = ShaclValidator.builder().build();
return (ValidationReport) invoke(validator, "validate", dataSource, shapesSail);
}
private static final class BlockingSail extends AbstractSail {
private final CountDownLatch latch = new CountDownLatch(1);
@Override
protected SailConnection getConnectionInternal() throws SailException {
try {
latch.await();
throw new AssertionError("Unexpected latch release");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new SailException("Interrupted while waiting for a connection", e);
}
}
@Override
protected void shutDownInternal() throws SailException {
latch.countDown();
}
@Override
public boolean isWritable() throws SailException {
return false;
}
@Override
public ValueFactory getValueFactory() {
return SimpleValueFactory.getInstance();
}
}
private static void assertHasMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
try {
clazz.getMethod(name, parameterTypes);
} catch (NoSuchMethodException e) {
fail("Expected method " + clazz.getName() + "#" + name, e);
}
}
private static Object invoke(Object target, String name, Object... args) throws Exception {
Method match = null;
for (Method candidate : target.getClass().getMethods()) {
if (!candidate.getName().equals(name)) {
continue;
}
Class<?>[] parameterTypes = candidate.getParameterTypes();
if (parameterTypes.length != args.length) {
continue;
}
boolean compatible = true;
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Object arg = args[i];
Class<?> argType = (arg == null) ? null : arg.getClass();
if (parameterType.isPrimitive()) {
parameterType = wrapPrimitive(parameterType);
}
if (argType != null && !parameterType.isAssignableFrom(argType)) {
compatible = false;
break;
}
}
if (compatible) {
match = candidate;
break;
}
}
if (match == null) {
fail("Expected compatible method " + target.getClass().getName() + "#" + name);
}
return match.invoke(target, args);
}
private static Class<?> wrapPrimitive(Class<?> type) {
if (type == boolean.class) {
return Boolean.class;
}
if (type == long.class) {
return Long.class;
}
if (type == int.class) {
return Integer.class;
}
if (type == double.class) {
return Double.class;
}
if (type == float.class) {
return Float.class;
}
if (type == short.class) {
return Short.class;
}
if (type == byte.class) {
return Byte.class;
}
if (type == char.class) {
return Character.class;
}
return type;
}
private static Object getFieldValue(Object target, String fieldName) {
Class<?> clazz = target.getClass();
while (clazz != null) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(target);
} catch (NoSuchFieldException e) {
clazz = clazz.getSuperclass();
} catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
throw new AssertionError("Field not found: " + target.getClass().getName() + "#" + fieldName);
}
private static void addShape(SailRepository repo, IRI graph, String requiredPropertyIri) throws Exception {
boolean isRdf4jShapeGraph = RDF4J.SHACL_SHAPE_GRAPH.equals(graph);
String mapping = isRdf4jShapeGraph
? ""
: "@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
"\n" +
"rdf4j:nil sh:shapesGraph <" + graph.stringValue() + "> .\n" +
"\n";
String ttl = "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
mapping +
"ex:Shape a sh:NodeShape ;\n" +
" sh:targetNode ex:a ;\n" +
" sh:property [\n" +
" sh:path <" + requiredPropertyIri + "> ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
try (SailRepositoryConnection conn = repo.getConnection()) {
conn.add(new StringReader(ttl), "", RDFFormat.TURTLE, graph);
}
}
private static void addData(SailRepository repo, String nodeIri, String propertyIri) throws Exception {
try (SailRepositoryConnection conn = repo.getConnection()) {
conn.add(iri(nodeIri), iri(propertyIri), iri("http://example.com/ns#value"));
}
}
private static void addManyViolationsShape(SailRepository repo, IRI graph) throws Exception {
boolean isRdf4jShapeGraph = RDF4J.SHACL_SHAPE_GRAPH.equals(graph);
String mapping = isRdf4jShapeGraph
? ""
: "@prefix rdf4j: <http://rdf4j.org/schema/rdf4j#> .\n" +
"\n" +
"rdf4j:nil sh:shapesGraph <" + graph.stringValue() + "> .\n" +
"\n";
String ttl = "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
mapping +
"ex:Shape a sh:NodeShape ;\n" +
" sh:targetClass ex:Person ;\n" +
" sh:property [\n" +
" sh:path ex:required ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
try (SailRepositoryConnection conn = repo.getConnection()) {
conn.add(new StringReader(ttl), "", RDFFormat.TURTLE, graph);
}
}
private static void addManyViolationsData(SailRepository repo, int count) throws Exception {
try (SailRepositoryConnection conn = repo.getConnection()) {
for (int i = 0; i < count; i++) {
conn.add(iri("http://example.com/ns#n" + i), RDF.TYPE, iri("http://example.com/ns#Person"));
}
}
}
private static void addTargetClassShape(SailRepository repo, IRI graph) throws Exception {
String ttl = "@prefix sh: <http://www.w3.org/ns/shacl#> .\n" +
"@prefix ex: <http://example.com/ns#> .\n" +
"\n" +
"ex:Shape a sh:NodeShape ;\n" +
" sh:targetClass ex:Parent ;\n" +
" sh:property [\n" +
" sh:path ex:required ;\n" +
" sh:minCount 1 ;\n" +
" ] .\n";
try (SailRepositoryConnection conn = repo.getConnection()) {
conn.add(new StringReader(ttl), "", RDFFormat.TURTLE, graph);
}
}
private static void addSubClassData(SailRepository repo) throws Exception {
try (SailRepositoryConnection conn = repo.getConnection()) {
conn.add(iri("http://example.com/ns#Child"), RDFS.SUBCLASSOF, iri("http://example.com/ns#Parent"));
conn.add(iri("http://example.com/ns#inst"), RDF.TYPE, iri("http://example.com/ns#Child"));
}
}
private static IRI iri(String iri) {
return org.eclipse.rdf4j.model.util.Values.iri(iri);
}
}