@@ -131,6 +131,28 @@ public void inferredTargetClassChangeIsDetectedWhenInferredStatementsReported()
131131 }
132132 }
133133
134+ @ Test
135+ public void inferredOnlyNotificationsStillTriggerValidation () throws Exception {
136+ SailRepository repo = createRepositoryWithExplicitStatementFiltering (true );
137+
138+ IRI ontologyGraph = repo .getValueFactory ().createIRI ("urn:ontology" );
139+ IRI dataGraph = repo .getValueFactory ().createIRI ("urn:data" );
140+
141+ loadTurtle (repo , ontologyTurtle (), ontologyGraph );
142+ loadTurtle (repo , railcarBrakeShapeTurtle (null , null ), RDF4J .SHACL_SHAPE_GRAPH );
143+
144+ IRI wagon1 = repo .getValueFactory ().createIRI ("urn:wagon1" );
145+ IRI freightWagon = repo .getValueFactory ().createIRI ("https://example.com/trains/FreightWagon" );
146+
147+ try (RepositoryConnection conn = repo .getConnection ()) {
148+ conn .begin ();
149+ conn .add (wagon1 , RDF .TYPE , freightWagon , dataGraph );
150+ assertThrows (ShaclSailValidationException .class , () -> commitAndRethrow (conn ));
151+ } finally {
152+ repo .shutDown ();
153+ }
154+ }
155+
134156 @ Test
135157 public void targetClassUsesPerShapeRdfsSubClassReasoning () throws Exception {
136158 SailRepository repo = createRepository (false , false , false );
@@ -279,6 +301,20 @@ private static SailRepository createRepositoryWithLegacyCallbackForwarding(boole
279301 return repo ;
280302 }
281303
304+ private static SailRepository createRepositoryWithExplicitStatementFiltering (boolean includeInferredStatements ) {
305+ NotifyingSail baseSail = new ExplicitStatementFilteringSail (new MemoryStore ());
306+ NotifyingSail shaclBase = new SchemaCachingRDFSInferencer (baseSail );
307+ ShaclSail shaclSail = new ShaclSail (shaclBase );
308+ shaclSail .setRdfsSubClassReasoning (false );
309+ shaclSail .setIncludeInferredStatements (includeInferredStatements );
310+ shaclSail .setEclipseRdf4jShaclExtensions (true );
311+ shaclSail .setSerializableValidation (false );
312+
313+ SailRepository repo = new SailRepository (shaclSail );
314+ repo .init ();
315+ return repo ;
316+ }
317+
282318 private static SailRepository createRepository (boolean includeInferredStatements ,
283319 boolean filterInferredNotifications ,
284320 boolean useSchemaCachingInferencer ) {
@@ -469,6 +505,68 @@ public void removeConnectionListener(SailConnectionListener listener) {
469505 }
470506 }
471507
508+ // Test helper: hide explicit change notifications from listeners.
509+ private static final class ExplicitStatementFilteringSail extends NotifyingSailWrapper {
510+ ExplicitStatementFilteringSail (NotifyingSail baseSail ) {
511+ super (baseSail );
512+ }
513+
514+ @ Override
515+ public NotifyingSailConnection getConnection () throws SailException {
516+ InferencerConnection connection = (InferencerConnection ) super .getConnection ();
517+ return new ExplicitStatementFilteringConnection (connection );
518+ }
519+ }
520+
521+ private static final class ExplicitStatementFilteringConnection extends InferencerConnectionWrapper {
522+ private final Map <SailConnectionListener , SailConnectionListener > listenerMap = new IdentityHashMap <>();
523+
524+ ExplicitStatementFilteringConnection (InferencerConnection wrappedCon ) {
525+ super (wrappedCon );
526+ }
527+
528+ @ Override
529+ public void addConnectionListener (SailConnectionListener listener ) {
530+ SailConnectionListener filteringListener = new SailConnectionListener () {
531+ @ Override
532+ public void statementAdded (Statement st ) {
533+ statementAdded (st , false );
534+ }
535+
536+ @ Override
537+ public void statementRemoved (Statement st ) {
538+ statementRemoved (st , false );
539+ }
540+
541+ @ Override
542+ public void statementAdded (Statement st , boolean inferred ) {
543+ if (inferred ) {
544+ listener .statementAdded (st , true );
545+ }
546+ }
547+
548+ @ Override
549+ public void statementRemoved (Statement st , boolean inferred ) {
550+ if (inferred ) {
551+ listener .statementRemoved (st , true );
552+ }
553+ }
554+ };
555+ listenerMap .put (listener , filteringListener );
556+ super .addConnectionListener (filteringListener );
557+ }
558+
559+ @ Override
560+ public void removeConnectionListener (SailConnectionListener listener ) {
561+ SailConnectionListener filteringListener = listenerMap .remove (listener );
562+ if (filteringListener != null ) {
563+ super .removeConnectionListener (filteringListener );
564+ } else {
565+ super .removeConnectionListener (listener );
566+ }
567+ }
568+ }
569+
472570 // Test helper: force all notifications through deprecated no-flag listener methods.
473571 private static final class LegacyCallbackForwardingSail extends NotifyingSailWrapper {
474572 LegacyCallbackForwardingSail (NotifyingSail baseSail ) {
0 commit comments