diff --git a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java index ea5ebdca14..4e0a51268d 100644 --- a/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java +++ b/core/sail/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelper.java @@ -11,12 +11,15 @@ package org.eclipse.rdf4j.sail.shacl; +import java.util.NoSuchElementException; + import org.eclipse.rdf4j.common.iteration.CloseableIteration; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; import org.eclipse.rdf4j.repository.RepositoryResult; +import org.eclipse.rdf4j.sail.InterruptedSailException; import org.eclipse.rdf4j.sail.SailConnection; import org.eclipse.rdf4j.sail.SailException; @@ -55,8 +58,23 @@ static void transferStatements(SailConnection from, TransferStatement transfer) try (CloseableIteration statements = from .getStatements(null, null, null, false)) { - while (statements.hasNext()) { - Statement next = statements.next(); + while (true) { + throwIfInterrupted(); + boolean hasNext = statements.hasNext(); + throwIfInterrupted(); + if (!hasNext) { + break; + } + + Statement next; + try { + next = statements.next(); + } catch (NoSuchElementException e) { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedSailException("Thread was interrupted while transferring statements.", e); + } + throw e; + } transfer.transfer(next.getSubject(), next.getPredicate(), next.getObject(), next.getContext()); @@ -66,6 +84,12 @@ static void transferStatements(SailConnection from, TransferStatement transfer) } + private static void throwIfInterrupted() { + if (Thread.currentThread().isInterrupted()) { + throw new InterruptedSailException("Thread was interrupted while transferring statements."); + } + } + static boolean isEmpty(SailConnection connection) { return !connection.hasStatement(null, null, null, false); } diff --git a/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelperTest.java b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelperTest.java new file mode 100644 index 0000000000..7613f8fcf5 --- /dev/null +++ b/core/sail/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/ConnectionHelperTest.java @@ -0,0 +1,78 @@ +/******************************************************************************* + * Copyright (c) 2026 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.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.NoSuchElementException; + +import org.eclipse.rdf4j.common.iteration.CloseableIteration; +import org.eclipse.rdf4j.model.Statement; +import org.eclipse.rdf4j.sail.InterruptedSailException; +import org.eclipse.rdf4j.sail.SailConnection; +import org.junit.jupiter.api.Test; + +class ConnectionHelperTest { + + @Test + void transferStatementsConvertsInterruptedIterationToInterruptedSailException() { + SailConnection connection = mock(SailConnection.class); + @SuppressWarnings("unchecked") + CloseableIteration statements = mock(CloseableIteration.class); + doReturn(statements).when(connection).getStatements(isNull(), isNull(), isNull(), anyBoolean()); + when(statements.hasNext()).thenReturn(true); + when(statements.next()).thenAnswer(invocation -> { + Thread.currentThread().interrupt(); + throw new NoSuchElementException("The iteration has been interrupted."); + }); + + try { + assertThatExceptionOfType(InterruptedSailException.class) + .isThrownBy(() -> ConnectionHelper.transferStatements(connection, (subject, predicate, object, + context) -> { + throw new AssertionError("No statement should be transferred after interrupt"); + })) + .withCauseInstanceOf(NoSuchElementException.class); + verify(statements).close(); + } finally { + Thread.interrupted(); + } + } + + @Test + void transferStatementsKeepsNonInterruptNoSuchElementException() { + SailConnection connection = mock(SailConnection.class); + @SuppressWarnings("unchecked") + CloseableIteration statements = mock(CloseableIteration.class); + NoSuchElementException exception = new NoSuchElementException("empty"); + doReturn(statements).when(connection).getStatements(isNull(), isNull(), isNull(), anyBoolean()); + when(statements.hasNext()).thenReturn(true); + when(statements.next()).thenThrow(exception); + + assertThatExceptionOfType(NoSuchElementException.class) + .isThrownBy(() -> ConnectionHelper.transferStatements(connection, (subject, predicate, object, + context) -> { + throw new AssertionError("No statement should be transferred"); + })) + .isSameAs(exception); + verify(statements).close(); + verify(statements, never()).remove(); + } +} diff --git a/site/content/download.md b/site/content/download.md index c37aa0080b..ebd406bc2d 100644 --- a/site/content/download.md +++ b/site/content/download.md @@ -5,15 +5,15 @@ toc: true You can either retrieve RDF4J via Apache Maven, or download the SDK or onejar directly. -## RDF4J 5.3.0 (latest) +## RDF4J 5.3.1 (latest) -RDF4J 5.3.0 is our latest stable release. It requires Java 11 minimally. -For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/5.3.0). +RDF4J 5.3.1 is our latest stable release. It requires Java 11 minimally. +For details on what’s new and how to upgrade, see the [release and upgrade notes](/release-notes/5.3.1). -- [RDF4J 5.3.0 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.0-sdk.zip)
+- [RDF4J 5.3.1 SDK (zip)](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.1-sdk.zip)
Full Eclipse RDF4J SDK, containing all libraries, RDF4J Server, Workbench, and Console applications, and Javadoc API. -- [RDF4J 5.3.0 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.0-onejar.jar)
+- [RDF4J 5.3.1 onejar](http://www.eclipse.org/downloads/download.php?file=/rdf4j/eclipse-rdf4j-5.3.1-onejar.jar)
Single jar file for easy inclusion of the full RDF4J toolkit in your Java project. - [RDF4J artifacts](https://search.maven.org/search?q=org.eclipse.rdf4j) on the [Maven Central Repository](http://search.maven.org/) @@ -28,7 +28,7 @@ You can include RDF4J as a Maven dependency in your Java project by including th org.eclipse.rdf4j rdf4j-bom - 5.3.0 + 5.3.1 pom import diff --git a/site/content/news/rdf4j-531.md b/site/content/news/rdf4j-531.md new file mode 100644 index 0000000000..9b8cd10db3 --- /dev/null +++ b/site/content/news/rdf4j-531.md @@ -0,0 +1,14 @@ +--- +title: "RDF4J 5.3.1 released" +date: 2026-04-23T21:24:25+0200 +layout: "single" +categories: ["news"] +--- +RDF4J 5.3.1 is now available. This is a patch release fixing 2 bugs. + +For more details, have a look at the [release notes](/release-notes/5.3.1). + +### Links + +- [Download RDF4J](/download/) +- [release notes](/release-notes/5.3.1) diff --git a/site/content/release-notes/5.3.1.md b/site/content/release-notes/5.3.1.md new file mode 100644 index 0000000000..3a59fddebb --- /dev/null +++ b/site/content/release-notes/5.3.1.md @@ -0,0 +1,11 @@ +--- +title: "5.3.1" +toc: true +--- +RDF4J 5.3.1 is a patch release that fixes 2 performance issues. + +For a complete overview, see [all issues fixed in 5.3.1](https://github.com/eclipse/rdf4j/milestone/129?closed=1). + +### Acknowledgements + +This release was made possible by contributions from [Håvard M. Ottestad](https://github.com/hmottestad). diff --git a/site/static/javadoc/5.3.1.tar.xz b/site/static/javadoc/5.3.1.tar.xz new file mode 100644 index 0000000000..9a1e0809ca Binary files /dev/null and b/site/static/javadoc/5.3.1.tar.xz differ diff --git a/site/static/javadoc/latest.tar.xz b/site/static/javadoc/latest.tar.xz index 89adac4264..9a1e0809ca 100644 Binary files a/site/static/javadoc/latest.tar.xz and b/site/static/javadoc/latest.tar.xz differ