Skip to content

Commit 1686245

Browse files
committed
wip
1 parent 9894f00 commit 1686245

5 files changed

Lines changed: 681 additions & 8 deletions

File tree

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java

Lines changed: 90 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ public class ShaclSailConnection extends NotifyingSailConnectionWrapper implemen
9494
private final HashSet<Statement> removedStatementsInferredSet = new HashSet<>();
9595

9696
private boolean shapeRefreshNeeded = false;
97+
private boolean legacyStatementAddedWithoutInferredFlagObserved = false;
98+
private boolean legacyStatementRemovedWithoutInferredFlagObserved = false;
9799
private boolean shapesModifiedInCurrentTransaction = false;
98100

99101
public final ShaclSail sail;
@@ -316,7 +318,7 @@ public void addStatement(UpdateContext modify, Resource subj, IRI pred, Value ob
316318
throws SailException {
317319
if (useDefaultShapesGraph && contexts.length == 1 && RDF4J.SHACL_SHAPE_GRAPH.equals(contexts[0])) {
318320
shapesRepoConnection.add(subj, pred, obj, contexts);
319-
shapeRefreshNeeded = true;
321+
markShapesRefreshNeeded();
320322
} else {
321323
super.addStatement(modify, subj, pred, obj, contexts);
322324
}
@@ -327,7 +329,7 @@ public void removeStatement(UpdateContext modify, Resource subj, IRI pred, Value
327329
throws SailException {
328330
if (useDefaultShapesGraph && contexts.length == 1 && RDF4J.SHACL_SHAPE_GRAPH.equals(contexts[0])) {
329331
shapesRepoConnection.remove(subj, pred, obj, contexts);
330-
shapeRefreshNeeded = true;
332+
markShapesRefreshNeeded();
331333
} else {
332334
super.removeStatement(modify, subj, pred, obj, contexts);
333335
}
@@ -337,7 +339,7 @@ public void removeStatement(UpdateContext modify, Resource subj, IRI pred, Value
337339
public void addStatement(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException {
338340
if (useDefaultShapesGraph && contexts.length == 1 && RDF4J.SHACL_SHAPE_GRAPH.equals(contexts[0])) {
339341
shapesRepoConnection.add(subj, pred, obj, contexts);
340-
shapeRefreshNeeded = true;
342+
markShapesRefreshNeeded();
341343
} else {
342344
super.addStatement(subj, pred, obj, contexts);
343345
}
@@ -347,7 +349,7 @@ public void addStatement(Resource subj, IRI pred, Value obj, Resource... context
347349
public void removeStatements(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException {
348350
if (useDefaultShapesGraph && contexts.length == 1 && RDF4J.SHACL_SHAPE_GRAPH.equals(contexts[0])) {
349351
shapesRepoConnection.remove(subj, pred, obj, contexts);
350-
shapeRefreshNeeded = true;
352+
markShapesRefreshNeeded();
351353
} else {
352354
super.removeStatements(subj, pred, obj, contexts);
353355
}
@@ -357,7 +359,7 @@ public void removeStatements(Resource subj, IRI pred, Value obj, Resource... con
357359
public void clear(Resource... contexts) throws SailException {
358360
if (Arrays.asList(contexts).contains(RDF4J.SHACL_SHAPE_GRAPH)) {
359361
shapesRepoConnection.clear();
360-
shapeRefreshNeeded = true;
362+
markShapesRefreshNeeded();
361363
}
362364
super.clear(contexts);
363365
}
@@ -488,6 +490,8 @@ private void cleanup() {
488490
stats = null;
489491
prepareHasBeenCalled = false;
490492
shapeRefreshNeeded = false;
493+
legacyStatementAddedWithoutInferredFlagObserved = false;
494+
legacyStatementRemovedWithoutInferredFlagObserved = false;
491495
shapesModifiedInCurrentTransaction = false;
492496

493497
currentIsolationLevel = null;
@@ -1375,6 +1379,7 @@ public void prepare() throws SailException {
13751379
}
13761380

13771381
List<ContextWithShape> shapesToValidate = shapesAfterRefresh != null ? shapesAfterRefresh : currentShapes;
1382+
validateLegacyCallbackInferredSupport(shapesToValidate);
13781383
boolean requiresRdfsSubClassReasoner = sail.isRdfsSubClassReasoning()
13791384
|| requiresRdfsSubClassReasoner(shapesToValidate);
13801385

@@ -1495,6 +1500,7 @@ private ValidationReport serializableValidation(List<ContextWithShape> shapesAft
14951500

14961501
@Override
14971502
public void statementAdded(Statement statement) {
1503+
legacyStatementAddedWithoutInferredFlagObserved = true;
14981504
statementAdded(statement, false);
14991505
}
15001506

@@ -1522,6 +1528,7 @@ public void statementAdded(Statement statement, boolean inferred) {
15221528

15231529
@Override
15241530
public void statementRemoved(Statement statement) {
1531+
legacyStatementRemovedWithoutInferredFlagObserved = true;
15251532
statementRemoved(statement, false);
15261533
}
15271534

@@ -1552,13 +1559,90 @@ private void checkIfShapesRefreshIsNeeded(Statement statement) {
15521559
if (!shapeRefreshNeeded) {
15531560
for (IRI shapesGraph : shapesGraphs) {
15541561
if (Objects.equals(statement.getContext(), shapesGraph)) {
1555-
shapeRefreshNeeded = true;
1562+
markShapesRefreshNeeded();
15561563
break;
15571564
}
15581565
}
15591566
}
15601567
}
15611568

1569+
private void markShapesRefreshNeeded() {
1570+
shapeRefreshNeeded = true;
1571+
}
1572+
1573+
private Boolean inferInferredFromStatementMetadata(Statement statement) {
1574+
try {
1575+
Boolean inferred = invokeBooleanStatementMethod(statement, "isInferred");
1576+
if (inferred != null) {
1577+
return inferred;
1578+
}
1579+
Boolean explicit = invokeBooleanStatementMethod(statement, "isExplicit");
1580+
if (explicit != null) {
1581+
return !explicit;
1582+
}
1583+
return null;
1584+
} catch (ReflectiveOperationException e) {
1585+
if (logger.isDebugEnabled()) {
1586+
logger.debug("Unable to infer inferred-flag from legacy callback statement metadata.", e);
1587+
}
1588+
return null;
1589+
}
1590+
}
1591+
1592+
private Boolean invokeBooleanStatementMethod(Statement statement, String methodName)
1593+
throws ReflectiveOperationException {
1594+
var method = statement.getClass().getMethod(methodName);
1595+
Class<?> returnType = method.getReturnType();
1596+
if (returnType != boolean.class && returnType != Boolean.class) {
1597+
return null;
1598+
}
1599+
Object value = method.invoke(statement);
1600+
return value == null ? null : (Boolean) value;
1601+
}
1602+
1603+
/**
1604+
* Reject legacy no-flag callbacks only when a shape explicitly disables inferred statements using
1605+
* rsx:includeInferredStatements=false. In that case, inferred-vs-explicit classification is required for correct
1606+
* validation and stores must use statementAdded/Removed callbacks with the inferred boolean argument.
1607+
*/
1608+
private void validateLegacyCallbackInferredSupport(List<ContextWithShape> shapesToValidate) {
1609+
if (!legacyStatementAddedWithoutInferredFlagObserved && !legacyStatementRemovedWithoutInferredFlagObserved) {
1610+
return;
1611+
}
1612+
1613+
boolean hasExplicitIncludeInferredDisabled = shapesToValidate.stream()
1614+
.filter(ContextWithShape::hasShape)
1615+
.map(ContextWithShape::getShape)
1616+
.map(Shape::getIncludeInferredStatementsOverride)
1617+
.anyMatch(Boolean.FALSE::equals);
1618+
1619+
if (!hasExplicitIncludeInferredDisabled) {
1620+
return;
1621+
}
1622+
1623+
String callbackDetails = getObservedLegacyCallbacksWithoutInferredFlag();
1624+
String message = "Underlying Sail does not support shapes that explicitly set "
1625+
+ "rsx:includeInferredStatements=false because it emits deprecated "
1626+
+ "SailConnectionListener callbacks without inferred flags (" + callbackDetails + "). "
1627+
+ "Implement statementAdded(Statement, boolean inferred) and "
1628+
+ "statementRemoved(Statement, boolean inferred).";
1629+
logger.error(message);
1630+
throw new ShaclSailValidationException(message);
1631+
}
1632+
1633+
private String getObservedLegacyCallbacksWithoutInferredFlag() {
1634+
if (legacyStatementAddedWithoutInferredFlagObserved && legacyStatementRemovedWithoutInferredFlagObserved) {
1635+
return "statementAdded(Statement), statementRemoved(Statement)";
1636+
}
1637+
if (legacyStatementAddedWithoutInferredFlagObserved) {
1638+
return "statementAdded(Statement)";
1639+
}
1640+
if (legacyStatementRemovedWithoutInferredFlagObserved) {
1641+
return "statementRemoved(Statement)";
1642+
}
1643+
return "<none>";
1644+
}
1645+
15621646
private void checkTransactionalValidationLimit() {
15631647
int changeCount = addedStatementsSet.size() + removedStatementsSet.size()
15641648
+ addedStatementsInferredSet.size() + removedStatementsInferredSet.size();

core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailValidationException.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ public class ShaclSailValidationException extends SailException implements Valid
3232
this.validationReport = validationReport;
3333
}
3434

35+
ShaclSailValidationException(String message) {
36+
super(message);
37+
this.validationReport = new ValidationReport(false);
38+
}
39+
3540
/**
3641
* @return A Model containing the validation report as specified by the SHACL Recommendation
3742
*/

0 commit comments

Comments
 (0)