diff --git a/.gitignore b/.gitignore
index 7d6d944acf2..06aa2c903bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,3 +56,4 @@ e2e/test-results
/tools/server/.lwjgl/
/tools/server/.lwjgl/
.m2_repo/
+/.m2/
diff --git a/compliance/elasticsearch/pom.xml b/compliance/elasticsearch/pom.xml
index 15d08fd83e9..0367a305336 100644
--- a/compliance/elasticsearch/pom.xml
+++ b/compliance/elasticsearch/pom.xml
@@ -37,34 +37,6 @@
${project.version}
test
-
- org.apache.lucene
- lucene-test-framework
- ${lucene.version}
- test
-
-
- org.hamcrest
- hamcrest-core
-
-
-
-
- org.elasticsearch.test
- framework
- ${elasticsearch.version}
- test
-
-
- commons-logging
- commons-logging
-
-
- org.apache.httpcomponents
- httpcore
-
-
-
org.apache.httpcomponents
httpcore
@@ -109,8 +81,28 @@
com.vividsolutions
jts
+
+ commons-logging
+ commons-logging
+
+
+ org.elasticsearch
+ jna
+
+
+ org.testcontainers
+ testcontainers
+ 1.19.7
+ test
+
+
+ org.testcontainers
+ elasticsearch
+ 1.19.7
+ test
+
org.apache.logging.log4j
log4j-core
diff --git a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexTest.java b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexTest.java
index 0a279ae7dc1..91e05682806 100644
--- a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexTest.java
+++ b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexTest.java
@@ -10,8 +10,14 @@
*******************************************************************************/
package org.eclipse.rdf4j.sail.elasticsearch;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.io.IOException;
-import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -31,19 +37,14 @@
import org.eclipse.rdf4j.sail.memory.MemoryStore;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.index.reindex.ReindexPlugin;
-import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
-import org.elasticsearch.test.ESIntegTestCase;
-import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-@ClusterScope(numDataNodes = 1)
-public class ElasticsearchIndexTest extends ESIntegTestCase {
+public class ElasticsearchIndexTest extends ElasticsearchTestContainerSupport {
private static final ValueFactory vf = SimpleValueFactory.getInstance();
@@ -97,39 +98,28 @@ public class ElasticsearchIndexTest extends ESIntegTestCase {
ElasticsearchIndex index;
@Before
- @Override
public void setUp() throws Exception {
- super.setUp();
- client = (TransportClient) internalCluster().transportClient();
+ client = createTransportClient();
Properties sailProperties = new Properties();
sailProperties.put(ElasticsearchIndex.TRANSPORT_KEY, client.transportAddresses().get(0).toString());
sailProperties.put(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "cluster.name",
client.settings().get("cluster.name"));
sailProperties.put(ElasticsearchIndex.INDEX_NAME_KEY, ElasticsearchTestUtils.getNextTestIndexName());
- sailProperties.put(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "green");
+ sailProperties.put(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
sailProperties.put(ElasticsearchIndex.WAIT_FOR_NODES_KEY, ">=1");
index = new ElasticsearchIndex();
index.initialize(sailProperties);
}
- @Override
- protected Collection> transportClientPlugins() {
- return List.of(ReindexPlugin.class);
- }
-
- @Override
- protected Collection> nodePlugins() {
- return List.of(ReindexPlugin.class);
- }
-
@After
- @Override
public void tearDown() throws Exception {
try {
- index.shutDown();
+ if (index != null) {
+ index.shutDown();
+ }
} finally {
- super.tearDown();
+ closeQuietly(client);
}
org.eclipse.rdf4j.common.concurrent.locks.Properties.setLockTrackingEnabled(false);
diff --git a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoSPARQLTest.java b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoSPARQLTest.java
index 27cbcb6fb55..f6e4251154b 100644
--- a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoSPARQLTest.java
+++ b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoSPARQLTest.java
@@ -10,34 +10,25 @@
*******************************************************************************/
package org.eclipse.rdf4j.sail.elasticsearch;
-import java.util.Collection;
-import java.util.List;
-
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.sail.lucene.LuceneSail;
import org.eclipse.testsuite.rdf4j.sail.lucene.AbstractLuceneSailGeoSPARQLTest;
import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.index.reindex.ReindexPlugin;
-import org.elasticsearch.plugins.Plugin;
-import org.elasticsearch.test.ESIntegTestCase;
-import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
-@ClusterScope(numDataNodes = 1)
-public class ElasticsearchSailGeoSPARQLTest extends ESIntegTestCase {
+public class ElasticsearchSailGeoSPARQLTest extends ElasticsearchTestContainerSupport {
AbstractLuceneSailGeoSPARQLTest delegateTest;
+ TransportClient client;
@Before
- @Override
public void setUp() throws Exception {
- super.setUp();
- TransportClient client = (TransportClient) internalCluster().transportClient();
+ client = createTransportClient();
delegateTest = new AbstractLuceneSailGeoSPARQLTest() {
@Override
@@ -47,30 +38,21 @@ protected void configure(LuceneSail sail) {
client.settings().get("cluster.name"));
sail.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, ElasticsearchTestUtils.getNextTestIndexName());
sail.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
- sail.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "green");
+ sail.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
sail.setParameter(ElasticsearchIndex.WAIT_FOR_NODES_KEY, ">=1");
}
};
delegateTest.setUp();
}
- @Override
- protected Collection> transportClientPlugins() {
- return List.of(ReindexPlugin.class);
- }
-
- @Override
- protected Collection> nodePlugins() {
- return List.of(ReindexPlugin.class);
- }
-
@After
- @Override
public void tearDown() throws Exception {
try {
- delegateTest.tearDown();
+ if (delegateTest != null) {
+ delegateTest.tearDown();
+ }
} finally {
- super.tearDown();
+ closeQuietly(client);
}
}
diff --git a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIndexedPropertiesTest.java b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIndexedPropertiesTest.java
index 686760b42f4..e119d152da7 100644
--- a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIndexedPropertiesTest.java
+++ b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIndexedPropertiesTest.java
@@ -10,33 +10,24 @@
*******************************************************************************/
package org.eclipse.rdf4j.sail.elasticsearch;
-import java.util.Collection;
-import java.util.List;
-
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.sail.lucene.LuceneSail;
import org.eclipse.testsuite.rdf4j.sail.lucene.AbstractLuceneSailIndexedPropertiesTest;
import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.index.reindex.ReindexPlugin;
-import org.elasticsearch.plugins.Plugin;
-import org.elasticsearch.test.ESIntegTestCase;
-import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-@ClusterScope(numDataNodes = 1)
-public class ElasticsearchSailIndexedPropertiesTest extends ESIntegTestCase {
+public class ElasticsearchSailIndexedPropertiesTest extends ElasticsearchTestContainerSupport {
AbstractLuceneSailIndexedPropertiesTest delegateTest;
+ TransportClient client;
@Before
- @Override
public void setUp() throws Exception {
- super.setUp();
- TransportClient client = (TransportClient) internalCluster().transportClient();
+ client = createTransportClient();
delegateTest = new AbstractLuceneSailIndexedPropertiesTest() {
@Override
@@ -46,30 +37,21 @@ protected void configure(LuceneSail sail) {
client.settings().get("cluster.name"));
sail.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, ElasticsearchTestUtils.getNextTestIndexName());
sail.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
- sail.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "green");
+ sail.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
sail.setParameter(ElasticsearchIndex.WAIT_FOR_NODES_KEY, ">=1");
}
};
delegateTest.setUp();
}
- @Override
- protected Collection> transportClientPlugins() {
- return List.of(ReindexPlugin.class);
- }
-
- @Override
- protected Collection> nodePlugins() {
- return List.of(ReindexPlugin.class);
- }
-
@After
- @Override
public void tearDown() throws Exception {
try {
- delegateTest.tearDown();
+ if (delegateTest != null) {
+ delegateTest.tearDown();
+ }
} finally {
- super.tearDown();
+ closeQuietly(client);
}
}
diff --git a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTest.java b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTest.java
index d436d6ab955..9016172e584 100644
--- a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTest.java
+++ b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTest.java
@@ -10,33 +10,24 @@
*******************************************************************************/
package org.eclipse.rdf4j.sail.elasticsearch;
-import java.util.Collection;
-import java.util.List;
-
import org.eclipse.rdf4j.query.MalformedQueryException;
import org.eclipse.rdf4j.query.QueryEvaluationException;
import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.sail.lucene.LuceneSail;
import org.eclipse.testsuite.rdf4j.sail.lucene.AbstractLuceneSailTest;
import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.index.reindex.ReindexPlugin;
-import org.elasticsearch.plugins.Plugin;
-import org.elasticsearch.test.ESIntegTestCase;
-import org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
-@ClusterScope(numDataNodes = 1)
-public class ElasticsearchSailTest extends ESIntegTestCase {
+public class ElasticsearchSailTest extends ElasticsearchTestContainerSupport {
AbstractLuceneSailTest delegateTest;
+ TransportClient client;
@Before
- @Override
public void setUp() throws Exception {
- super.setUp();
- TransportClient client = (TransportClient) internalCluster().transportClient();
+ client = createTransportClient();
delegateTest = new AbstractLuceneSailTest() {
@Override
@@ -46,30 +37,21 @@ protected void configure(LuceneSail sail) {
client.settings().get("cluster.name"));
sail.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, ElasticsearchTestUtils.getNextTestIndexName());
sail.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
- sail.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "green");
+ sail.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
sail.setParameter(ElasticsearchIndex.WAIT_FOR_NODES_KEY, ">=1");
}
};
delegateTest.setUp();
}
- @Override
- protected Collection> transportClientPlugins() {
- return List.of(ReindexPlugin.class);
- }
-
- @Override
- protected Collection> nodePlugins() {
- return List.of(ReindexPlugin.class);
- }
-
@After
- @Override
public void tearDown() throws Exception {
try {
- delegateTest.tearDown();
+ if (delegateTest != null) {
+ delegateTest.tearDown();
+ }
} finally {
- super.tearDown();
+ closeQuietly(client);
}
}
diff --git a/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchTestContainerSupport.java b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchTestContainerSupport.java
new file mode 100644
index 00000000000..aab99d0ab06
--- /dev/null
+++ b/compliance/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchTestContainerSupport.java
@@ -0,0 +1,154 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.time.Duration;
+
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.elasticsearch.client.transport.NoNodeAvailableException;
+import org.elasticsearch.client.transport.TransportClient;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.transport.TransportAddress;
+import org.elasticsearch.core.TimeValue;
+import org.elasticsearch.transport.client.PreBuiltTransportClient;
+import org.junit.Assume;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
+import org.testcontainers.utility.DockerImageName;
+
+/**
+ * Shared Testcontainers support for Elasticsearch integration tests.
+ */
+public abstract class ElasticsearchTestContainerSupport {
+
+ private static final String ELASTICSEARCH_IMAGE = "docker.elastic.co/elasticsearch/elasticsearch:7.15.2";
+ private static final String CLUSTER_NAME = "rdf4j-test-cluster";
+ private static final int TRANSPORT_PORT = 9300;
+ private static final TimeValue HEALTH_TIMEOUT = TimeValue.timeValueSeconds(30);
+
+ private static final Object CONTAINER_LOCK = new Object();
+ private static ElasticsearchContainer container;
+ private static boolean dockerAvailable = true;
+ private static boolean shutdownHookRegistered;
+
+ private static ElasticsearchContainer createContainer() {
+ DockerImageName imageName = DockerImageName.parse(ELASTICSEARCH_IMAGE)
+ .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch");
+
+ ElasticsearchContainer container = new ElasticsearchContainer(imageName)
+ .withEnv("cluster.name", CLUSTER_NAME)
+ .withEnv("discovery.type", "single-node")
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("xpack.security.transport.ssl.enabled", "false")
+ .withEnv("xpack.security.http.ssl.enabled", "false")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withStartupTimeout(Duration.ofMinutes(2));
+ container.setStartupAttempts(3);
+ return container;
+ }
+
+ protected TransportClient createTransportClient() throws UnknownHostException {
+ ElasticsearchContainer elasticsearchContainer = getOrStartContainer();
+
+ Settings settings = Settings.builder()
+ .put("cluster.name", CLUSTER_NAME)
+ .put("client.transport.sniff", false)
+ .build();
+
+ TransportAddress address = new TransportAddress(
+ InetAddress.getByName(elasticsearchContainer.getHost()),
+ elasticsearchContainer.getMappedPort(TRANSPORT_PORT));
+
+ TransportClient client = new PreBuiltTransportClient(settings)
+ .addTransportAddress(address);
+
+ waitForClusterReadiness(client);
+ return client;
+ }
+
+ protected String getClusterName() {
+ return CLUSTER_NAME;
+ }
+
+ protected void waitForClusterReadiness(TransportClient client) {
+ long deadline = System.nanoTime() + HEALTH_TIMEOUT.nanos();
+ NoNodeAvailableException lastException = null;
+ while (System.nanoTime() < deadline) {
+ try {
+ ClusterHealthResponse health = client.admin()
+ .cluster()
+ .prepareHealth()
+ .setWaitForYellowStatus()
+ .setTimeout(TimeValue.timeValueSeconds(30))
+ .get();
+ if (!health.isTimedOut()) {
+ return;
+ }
+ } catch (NoNodeAvailableException e) {
+ lastException = e;
+ }
+
+ try {
+ Thread.sleep(1_000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted while waiting for Elasticsearch testcontainer", e);
+ }
+ }
+
+ if (lastException != null) {
+ throw new IllegalStateException("Elasticsearch test container not reachable", lastException);
+ }
+ throw new IllegalStateException("Elasticsearch cluster did not reach yellow status within timeout");
+ }
+
+ protected static void closeQuietly(TransportClient client) {
+ if (client != null) {
+ client.close();
+ }
+ }
+
+ private static ElasticsearchContainer getOrStartContainer() {
+ if (!dockerAvailable) {
+ Assume.assumeTrue("Docker not available for Elasticsearch tests", false);
+ }
+ synchronized (CONTAINER_LOCK) {
+ if (container == null) {
+ ElasticsearchContainer candidate = createContainer();
+ try {
+ candidate.start();
+ container = candidate;
+ afterContainerStarted();
+ System.out.println("ES Started");
+ } catch (Throwable t) {
+ dockerAvailable = false;
+ Assume.assumeNoException("Docker not available for Elasticsearch tests", t);
+ }
+ }
+ return container;
+ }
+ }
+
+ private static void afterContainerStarted() {
+ if (!shutdownHookRegistered) {
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+ synchronized (CONTAINER_LOCK) {
+ if (container != null && container.isRunning()) {
+ container.stop();
+ System.out.println("ES Stopped");
+ }
+ }
+ }));
+ shutdownHookRegistered = true;
+ }
+ }
+}
diff --git a/core/sail/elasticsearch-store/pom.xml b/core/sail/elasticsearch-store/pom.xml
index dfa8674e7e3..3b47e915d02 100644
--- a/core/sail/elasticsearch-store/pom.xml
+++ b/core/sail/elasticsearch-store/pom.xml
@@ -24,6 +24,10 @@
commons-logging
commons-logging
+
+ org.elasticsearch
+ jna
+
@@ -41,6 +45,10 @@
commons-logging
commons-logging
+
+ org.elasticsearch
+ jna
+
@@ -75,6 +83,18 @@
logback-classic
test
+
+ org.testcontainers
+ testcontainers
+ 1.19.7
+ test
+
+
+ org.testcontainers
+ elasticsearch
+ 1.19.7
+ test
+
org.openjdk.jmh
jmh-core
@@ -130,46 +150,6 @@
-Djava.security.manager=allow
-
- skipIfSkipITs
-
-
- skipITs
- true
-
-
-
-
-
- com.github.alexcojocaru
- elasticsearch-maven-plugin
-
- true
-
-
-
-
-
-
- skipIfSkipTests
-
-
- skipTests
- true
-
-
-
-
-
- com.github.alexcojocaru
- elasticsearch-maven-plugin
-
- true
-
-
-
-
-
@@ -180,45 +160,6 @@
0
-
- com.github.alexcojocaru
- elasticsearch-maven-plugin
- 6.28
-
- ${skipITs}
- ${skipTests}
- ${elasticsearch.version}
- test
- 9300
- 9200
-
- false
- ${java.sec.mgr} -Xmx1G -Xms1G
-
- 1
-
-
- false
-
-
-
-
-
- start-elasticsearch
- pre-integration-test
-
- runforked
-
-
-
- stop-elasticsearch
- post-integration-test
-
- stop
-
-
-
-
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/AbstractElasticsearchStoreIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/AbstractElasticsearchStoreIT.java
index 708e0166d4b..26a4eb8eddc 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/AbstractElasticsearchStoreIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/AbstractElasticsearchStoreIT.java
@@ -11,22 +11,22 @@
package org.eclipse.rdf4j.sail.elasticsearchstore;
import java.io.IOException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
+import org.elasticsearch.ElasticsearchStatusException;
+import org.elasticsearch.action.admin.indices.refresh.RefreshRequest;
+import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.Request;
import org.elasticsearch.client.RequestOptions;
-import org.elasticsearch.client.Requests;
-import org.elasticsearch.client.transport.TransportClient;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.transport.TransportAddress;
+import org.elasticsearch.client.indices.GetIndexRequest;
+import org.elasticsearch.client.indices.GetIndexResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
-import org.elasticsearch.transport.client.PreBuiltTransportClient;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -34,19 +34,31 @@ public abstract class AbstractElasticsearchStoreIT {
private static final Logger logger = LoggerFactory.getLogger(AbstractElasticsearchStoreIT.class);
+ private static boolean dockerAvailable;
+
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
+ dockerAvailable = TestHelpers.openClient();
}
@AfterAll
public static void afterClass() throws IOException {
- TestHelpers.closeClient();
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterEach
public void after() throws IOException {
- TestHelpers.getClient().indices().refresh(Requests.refreshRequest("*"), RequestOptions.DEFAULT);
+ if (!dockerAvailable) {
+ return;
+ }
+ TestHelpers.getClient().indices().refresh(new RefreshRequest("*"), RequestOptions.DEFAULT);
printAllDocs();
deleteAllIndexes();
}
@@ -56,7 +68,7 @@ protected void printAllDocs() throws IOException {
if (!index.equals(".geoip_databases")) {
logger.info("INDEX: " + index);
SearchResponse res = TestHelpers.getClient()
- .search(Requests.searchRequest(index), RequestOptions.DEFAULT);
+ .search(new SearchRequest(index), RequestOptions.DEFAULT);
SearchHits hits = res.getHits();
for (SearchHit hit : hits) {
logger.info(" doc " + hit.getSourceAsString());
@@ -69,24 +81,24 @@ protected void deleteAllIndexes() throws IOException {
for (String index : getIndexes()) {
if (!index.equals(".geoip_databases")) {
logger.info("deleting index: " + index);
- TestHelpers.getClient().indices().delete(Requests.deleteIndexRequest(index), RequestOptions.DEFAULT);
+ TestHelpers.getClient().getLowLevelClient().performRequest(new Request("DELETE", "/" + index));
}
}
}
- protected String[] getIndexes() {
- Settings settings = Settings.builder().put("cluster.name", TestHelpers.CLUSTER).build();
- try (TransportClient client = new PreBuiltTransportClient(settings)) {
- client.addTransportAddress(
- new TransportAddress(InetAddress.getByName("localhost"), TestHelpers.PORT));
-
- return client.admin()
- .indices()
- .getIndex(new GetIndexRequest())
- .actionGet()
- .getIndices();
- } catch (UnknownHostException e) {
- throw new IllegalStateException(e);
+ protected String[] getIndexes() throws IOException {
+ if (!dockerAvailable) {
+ return new String[0];
+ }
+ GetIndexRequest request = new GetIndexRequest("*");
+ try {
+ if (!TestHelpers.getClient().indices().exists(request, RequestOptions.DEFAULT)) {
+ return new String[0];
+ }
+ GetIndexResponse response = TestHelpers.getClient().indices().get(request, RequestOptions.DEFAULT);
+ return response.getIndices();
+ } catch (ElasticsearchStatusException e) {
+ return new String[0];
}
}
}
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientProviderWithDebugStats.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientProviderWithDebugStats.java
index 25f14f807cc..52cc18d4d69 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientProviderWithDebugStats.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientProviderWithDebugStats.java
@@ -29,7 +29,10 @@ public class ClientProviderWithDebugStats implements ClientProvider {
public ClientProviderWithDebugStats(String hostname, int port, String clusterName) {
try {
- Settings settings = Settings.builder().put("cluster.name", clusterName).build();
+ Settings settings = Settings.builder()
+ .put("cluster.name", clusterName)
+ .put("client.transport.sniff", false)
+ .build();
TransportClient client = new PreBuiltTransportClient(settings);
client.addTransportAddress(new TransportAddress(InetAddress.getByName(hostname), port));
this.client = new ClientWithStats(client);
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientWithStats.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientWithStats.java
index 21c818fa73b..baff2df632b 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientWithStats.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ClientWithStats.java
@@ -368,4 +368,4 @@ public void close() {
public long getBulkCalls() {
return bulkCalls;
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreIT.java
index 7d3a3da9b4a..6811a521f92 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreIT.java
@@ -38,15 +38,15 @@ public class ElasticsearchStoreIT extends AbstractElasticsearchStoreIT {
@Test
public void testInstantiate() {
- ElasticsearchStore elasticsearchStore = new ElasticsearchStore("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER, "testindex");
+ ElasticsearchStore elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName(), "testindex");
elasticsearchStore.shutDown();
}
@Test
public void testGetConnection() {
- ElasticsearchStore elasticsearchStore = new ElasticsearchStore("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER, "testindex");
+ ElasticsearchStore elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName(), "testindex");
try (NotifyingSailConnection connection = elasticsearchStore.getConnection()) {
}
elasticsearchStore.shutDown();
@@ -56,14 +56,16 @@ public void testGetConnection() {
@Test
public void testSailRepository() {
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
elasticsearchStore.shutDown();
}
@Test
public void testGetSailRepositoryConnection() {
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
}
elasticsearchStore.shutDown();
@@ -71,15 +73,16 @@ public void testGetSailRepositoryConnection() {
@Test
public void testShutdownAndRecreate() {
- ElasticsearchStore elasticsearchStore = new ElasticsearchStore("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER, "testindex");
+ ElasticsearchStore elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName(), "testindex");
try (NotifyingSailConnection connection = elasticsearchStore.getConnection()) {
connection.begin(IsolationLevels.NONE);
connection.addStatement(RDF.TYPE, RDF.TYPE, RDFS.RESOURCE);
connection.commit();
}
elasticsearchStore.shutDown();
- elasticsearchStore = new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER,
+ elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(),
"testindex");
try (NotifyingSailConnection connection = elasticsearchStore.getConnection()) {
connection.begin(IsolationLevels.NONE);
@@ -92,8 +95,8 @@ public void testShutdownAndRecreate() {
@Test
public void testShutdownAndReinit() {
- ElasticsearchStore elasticsearchStore = new ElasticsearchStore("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER, "testindex");
+ ElasticsearchStore elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName(), "testindex");
try (NotifyingSailConnection connection = elasticsearchStore.getConnection()) {
connection.begin(IsolationLevels.NONE);
connection.addStatement(RDF.TYPE, RDF.TYPE, RDFS.RESOURCE);
@@ -106,8 +109,8 @@ public void testShutdownAndReinit() {
@Test
public void testAddRemoveData() {
- ElasticsearchStore elasticsearchStore = new ElasticsearchStore("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER, "testindex");
+ ElasticsearchStore elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName(), "testindex");
try (NotifyingSailConnection connection = elasticsearchStore.getConnection()) {
connection.begin(IsolationLevels.NONE);
connection.addStatement(RDF.TYPE, RDF.TYPE, RDFS.RESOURCE);
@@ -128,7 +131,8 @@ public void testAddRemoveData() {
public void testAddLargeDataset() {
StopWatch stopWatch = StopWatch.createStarted();
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
stopWatch.stop();
@@ -173,7 +177,8 @@ public void testGC() {
}
private ClientProvider initElasticsearchStoreForGcTest() {
- ElasticsearchStore sail = new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER,
+ ElasticsearchStore sail = new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(),
"testindex");
ClientProvider clientProvider = sail.clientProvider;
@@ -189,7 +194,8 @@ private ClientProvider initElasticsearchStoreForGcTest() {
public void testNamespacePersistenc() {
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
connection.begin();
@@ -199,7 +205,8 @@ public void testNamespacePersistenc() {
elasticsearchStore.shutDown();
elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
String namespace = connection.getNamespace(SHACL.PREFIX);
@@ -207,4 +214,4 @@ public void testNamespacePersistenc() {
}
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreTransactionsIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreTransactionsIT.java
index 6a2b5a8a255..a26e84705f6 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreTransactionsIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreTransactionsIT.java
@@ -57,7 +57,8 @@ public class ElasticsearchStoreTransactionsIT extends AbstractElasticsearchStore
@BeforeEach
public void before() {
- elasticsearchStore = new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex");
+ elasticsearchStore = new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex");
elasticsearchStore.setElasticsearchScrollTimeout(60000);
try (NotifyingSailConnection connection = elasticsearchStore.getConnection()) {
@@ -718,4 +719,4 @@ private Set asSet(Statement... statements) {
return set;
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreWalIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreWalIT.java
index 751e166caf2..d8317bc301b 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreWalIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/ElasticsearchStoreWalIT.java
@@ -48,7 +48,8 @@ public void testAddLargeDataset() {
assertTrue(transactionFaild);
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
@@ -61,8 +62,8 @@ public void testAddLargeDataset() {
}
private void failedTransactionAdd(int count) {
- ClientProviderWithDebugStats clientProvider = new ClientProviderWithDebugStats("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER);
+ ClientProviderWithDebugStats clientProvider = new ClientProviderWithDebugStats(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName());
ElasticsearchStore es = new ElasticsearchStore(clientProvider, "testindex");
SailRepository elasticsearchStore = new SailRepository(es);
@@ -113,7 +114,8 @@ public void testRemoveLargeDataset() {
assertTrue(transactionFaild);
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
@@ -127,7 +129,8 @@ public void testRemoveLargeDataset() {
private void fill(int count) {
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
@@ -142,8 +145,8 @@ private void fill(int count) {
}
private void failedTransactionRemove() {
- ClientProviderWithDebugStats clientProvider = new ClientProviderWithDebugStats("localhost",
- TestHelpers.PORT, TestHelpers.CLUSTER);
+ ClientProviderWithDebugStats clientProvider = new ClientProviderWithDebugStats(TestHelpers.getHost(),
+ TestHelpers.getTransportPort(), TestHelpers.getClusterName());
ElasticsearchStore es = new ElasticsearchStore(clientProvider, "testindex");
SailRepository elasticsearchStore = new SailRepository(es);
@@ -175,4 +178,4 @@ private void failedTransactionRemove() {
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/InferenceIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/InferenceIT.java
index 64300c174cb..1d42f166a54 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/InferenceIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/InferenceIT.java
@@ -25,6 +25,7 @@
import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
import org.eclipse.rdf4j.sail.inferencer.fc.SchemaCachingRDFSInferencer;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
@@ -40,13 +41,17 @@ public class InferenceIT extends AbstractElasticsearchStoreIT {
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- singletonClientProvider = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
+ singletonClientProvider = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
}
@AfterAll
public static void afterClassSingleton() throws Exception {
- singletonClientProvider.close();
+ if (singletonClientProvider != null) {
+ singletonClientProvider.close();
+ singletonClientProvider = null;
+ }
TestHelpers.closeClient();
}
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/TestHelpers.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/TestHelpers.java
index a582cc6d669..f5f72d64e8f 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/TestHelpers.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/TestHelpers.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2019 Eclipse RDF4J contributors.
+ * 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
@@ -11,27 +11,171 @@
package org.eclipse.rdf4j.sail.elasticsearchstore;
import java.io.IOException;
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
import org.apache.http.HttpHost;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.core.TimeValue;
+import org.junit.jupiter.api.Assumptions;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
+import org.testcontainers.utility.DockerImageName;
-public class TestHelpers {
- public static final String CLUSTER = "test";
- public static final int PORT = 9300;
+/**
+ * Shared helper for Elasticsearch store integration tests.
+ */
+public final class TestHelpers {
- private static RestHighLevelClient CLIENT;
+ private static final String CLUSTER = "rdf4j-elasticsearch-store-test";
+ private static final int DEFAULT_HTTP_PORT = 9200;
+ private static final int DEFAULT_TRANSPORT_PORT = 9300;
+ private static final DockerImageName ELASTICSEARCH_IMAGE = DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch:7.15.2")
+ .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch");
- public static void openClient() {
- CLIENT = new RestHighLevelClient(RestClient.builder(new HttpHost("localhost", 9200, "http")));
+ private static ElasticsearchContainer container;
+ private static RestHighLevelClient client;
+
+ private TestHelpers() {
+ // static helper
+ }
+
+ public static synchronized boolean openClient() {
+ if (client != null) {
+ return true;
+ }
+
+ if (!ensureContainerStarted()) {
+ return false;
+ }
+
+ client = new RestHighLevelClient(RestClient.builder(
+ new HttpHost(container.getHost(), container.getMappedPort(DEFAULT_HTTP_PORT), "http")));
+
+ if (!waitForClusterReadiness()) {
+ closeQuietly();
+ return false;
+ }
+
+ return true;
}
- public static RestHighLevelClient getClient() {
- return CLIENT;
+ private static boolean ensureContainerStarted() {
+ if (container != null) {
+ return true;
+ }
+
+ ElasticsearchContainer candidate = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
+ .withEnv("cluster.name", CLUSTER)
+ .withEnv("discovery.type", "single-node")
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("xpack.security.transport.ssl.enabled", "false")
+ .withEnv("xpack.security.http.ssl.enabled", "false")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withStartupTimeout(Duration.ofMinutes(2));
+ candidate.setStartupAttempts(3);
+
+ try {
+ candidate.start();
+ container = candidate;
+ return true;
+ } catch (Throwable t) {
+ container = null;
+ return false;
+ }
+ }
+
+ private static boolean waitForClusterReadiness() {
+ long deadline = System.nanoTime() + TimeUnit.MINUTES.toNanos(2);
+ ClusterHealthRequest request = new ClusterHealthRequest().waitForYellowStatus();
+ request.timeout(TimeValue.timeValueSeconds(30));
+ Exception last = null;
+
+ while (System.nanoTime() < deadline) {
+ try {
+ ClusterHealthResponse response = client.cluster().health(request, RequestOptions.DEFAULT);
+ if (!response.isTimedOut()) {
+ return true;
+ }
+ } catch (Exception e) {
+ last = e;
+ }
+
+ try {
+ Thread.sleep(1_000);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ closeQuietly();
+ throw new IllegalStateException("Interrupted while waiting for Elasticsearch test container", e);
+ }
+ }
+
+ closeQuietly();
+ return false;
}
- public static void closeClient() throws IOException {
- CLIENT.close();
+ private static synchronized void closeQuietly() {
+ if (client != null) {
+ try {
+ client.close();
+ } catch (IOException ignored) {
+ }
+ client = null;
+ }
+ if (container != null) {
+ container.stop();
+ container = null;
+ }
}
+ public static synchronized RestHighLevelClient getClient() {
+ if (!openClient()) {
+ throw new IllegalStateException("Elasticsearch test container not started");
+ }
+ return client;
+ }
+
+ public static synchronized void closeClient() throws IOException {
+ if (client != null) {
+ client.close();
+ client = null;
+ }
+ if (container != null) {
+ container.stop();
+ container = null;
+ }
+ }
+
+ public static synchronized String getHost() {
+ if (!openClient()) {
+ throw new IllegalStateException("Elasticsearch test container not started");
+ }
+ return container.getHost();
+ }
+
+ public static synchronized int getHttpPort() {
+ if (!openClient()) {
+ throw new IllegalStateException("Elasticsearch test container not started");
+ }
+ return container.getMappedPort(DEFAULT_HTTP_PORT);
+ }
+
+ public static synchronized int getTransportPort() {
+ if (!openClient()) {
+ throw new IllegalStateException("Elasticsearch test container not started");
+ }
+ return container.getMappedPort(DEFAULT_TRANSPORT_PORT);
+ }
+
+ public static String getClusterName() {
+ return CLUSTER;
+ }
+
+ public static ElasticsearchStore createStore(String index) {
+ return new ElasticsearchStore(getHost(), getTransportPort(), getClusterName(), index);
+ }
}
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/AddBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/AddBenchmark.java
index f4f5cb0f0ef..954506a62f2 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/AddBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/AddBenchmark.java
@@ -56,7 +56,8 @@ public void beforeClass() {
// PATH
TestHelpers.getClient();
elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex",
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex",
ExtensibleStore.Cache.NONE));
System.gc();
@@ -101,4 +102,4 @@ public void clearAndAddLargeFileReadCommitted() throws IOException {
}
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/DeleteBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/DeleteBenchmark.java
index 72e8f48d95b..d54ef7d61f8 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/DeleteBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/DeleteBenchmark.java
@@ -23,6 +23,7 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.ElasticsearchStore;
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.sail.extensiblestore.ExtensibleStore;
+import org.junit.jupiter.api.Assumptions;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -58,10 +59,11 @@ public void beforeClass() {
// JMH does not correctly set JAVA_HOME. Change the JAVA_HOME below if you the following error:
// [EmbeddedElsHandler] INFO p.a.t.e.ElasticServer - could not find java; set JAVA_HOME or ensure java is in
// PATH
- TestHelpers.openClient();
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex",
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex",
ExtensibleStore.Cache.NONE));
System.gc();
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/InitBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/InitBenchmark.java
index 8cd2e7fedf4..029393db29d 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/InitBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/InitBenchmark.java
@@ -22,6 +22,7 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.SingletonClientProvider;
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.sail.extensiblestore.ExtensibleStore;
+import org.junit.jupiter.api.Assumptions;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -55,15 +56,19 @@ public void beforeClass() {
// JMH does not correctly set JAVA_HOME. Change the JAVA_HOME below if you the following error:
// [EmbeddedElsHandler] INFO p.a.t.e.ElasticServer - could not find java; set JAVA_HOME or ensure java is in
// PATH
- TestHelpers.openClient();
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
System.gc();
}
@TearDown(Level.Trial)
public void afterClass() throws Exception {
- clientPool.close();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
TestHelpers.closeClient();
}
@@ -71,7 +76,8 @@ public void afterClass() throws Exception {
public void initWithElasticsearchClientCreation() {
SailRepository elasticsearchStore = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex",
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex",
ExtensibleStore.Cache.NONE));
try (SailRepositoryConnection connection = elasticsearchStore.getConnection()) {
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/Main.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/Main.java
index 80051aef055..6b81d8db149 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/Main.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/Main.java
@@ -29,4 +29,4 @@ public static void main(String[] args) throws RunnerException {
new Runner(opt).run();
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/QueryBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/QueryBenchmark.java
index fbb7019834c..e50cade09af 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/QueryBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/QueryBenchmark.java
@@ -30,6 +30,7 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.ElasticsearchStore;
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.sail.extensiblestore.ExtensibleStore;
+import org.junit.jupiter.api.Assumptions;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -86,10 +87,11 @@ public void beforeClass() throws IOException {
// JMH does not correctly set JAVA_HOME. Change the JAVA_HOME below if you the following error:
// [EmbeddedElsHandler] INFO p.a.t.e.ElasticServer - could not find java; set JAVA_HOME or ensure java is in
// PATH
- TestHelpers.openClient();
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
repository = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex",
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex",
ExtensibleStore.Cache.NONE));
try (SailRepositoryConnection connection = repository.getConnection()) {
connection.begin(IsolationLevels.NONE);
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/ReadCacheBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/ReadCacheBenchmark.java
index 0c73caceae1..258a635ede9 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/ReadCacheBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/ReadCacheBenchmark.java
@@ -30,6 +30,7 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.ElasticsearchStore;
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.sail.extensiblestore.ExtensibleStore;
+import org.junit.jupiter.api.Assumptions;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -76,13 +77,15 @@ public void beforeClass() throws IOException {
// JMH does not correctly set JAVA_HOME. Change the JAVA_HOME below if you the following error:
// [EmbeddedElsHandler] INFO p.a.t.e.ElasticServer - could not find java; set JAVA_HOME or ensure java is in
// PATH
- TestHelpers.openClient();
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
- repoWithoutCache = new SailRepository(new ElasticsearchStore("localhost", TestHelpers.PORT,
- TestHelpers.CLUSTER, "testindex1", ExtensibleStore.Cache.NONE));
+ repoWithoutCache = new SailRepository(
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex1", ExtensibleStore.Cache.NONE));
repoWithCache = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex2"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex2"));
try (SailRepositoryConnection connection = repoWithCache.getConnection()) {
connection.begin(IsolationLevels.NONE);
connection.clear();
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionBenchmark.java
index 12e3c3850b2..2e4c4245392 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionBenchmark.java
@@ -24,6 +24,7 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.ElasticsearchStore;
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.sail.extensiblestore.ExtensibleStore;
+import org.junit.jupiter.api.Assumptions;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -54,10 +55,11 @@ public void beforeClass() throws IOException {
// JMH does not correctly set JAVA_HOME. Change the JAVA_HOME below if you the following error:
// [EmbeddedElsHandler] INFO p.a.t.e.ElasticServer - could not find java; set JAVA_HOME or ensure java is in
// PATH
- TestHelpers.openClient();
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
repository = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex",
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex",
ExtensibleStore.Cache.NONE));
try (SailRepositoryConnection connection = repository.getConnection()) {
connection.begin(IsolationLevels.NONE);
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionParallelBenchmark.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionParallelBenchmark.java
index 24898bc08ed..7c7f0e9d684 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionParallelBenchmark.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/benchmark/TransactionParallelBenchmark.java
@@ -26,6 +26,7 @@
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.sail.elasticsearchstore.ElasticsearchStore;
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
+import org.junit.jupiter.api.Assumptions;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
@@ -57,10 +58,11 @@ public void beforeClass() throws IOException {
// JMH does not correctly set JAVA_HOME. Change the JAVA_HOME below if you the following error:
// [EmbeddedElsHandler] INFO p.a.t.e.ElasticServer - could not find java; set JAVA_HOME or ensure java is in
// PATH
- TestHelpers.openClient();
+ Assumptions.assumeTrue(TestHelpers.openClient(), "Docker not available for Elasticsearch tests");
repository = new SailRepository(
- new ElasticsearchStore("localhost", TestHelpers.PORT, TestHelpers.CLUSTER, "testindex"));
+ new ElasticsearchStore(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName(), "testindex"));
try (SailRepositoryConnection connection = repository.getConnection()) {
connection.begin(IsolationLevels.NONE);
connection.add(getResourceAsStream("benchmarkFiles/datagovbe-valid.ttl"), "", RDFFormat.TURTLE);
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchGraphQueryResultIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchGraphQueryResultIT.java
index 7c0de3a1b54..15a63eca0e3 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchGraphQueryResultIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchGraphQueryResultIT.java
@@ -17,22 +17,39 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.repository.GraphQueryResultTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
public class ElasticsearchGraphQueryResultIT extends GraphQueryResultTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConcurrencyIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConcurrencyIT.java
index 460aec582cc..fd7134bb34a 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConcurrencyIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConcurrencyIT.java
@@ -18,7 +18,9 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.sail.SailConcurrencyTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
/**
* An extension of {@link SailConcurrencyTest} for testing the class
@@ -27,17 +29,32 @@
public class ElasticsearchStoreConcurrencyIT extends SailConcurrencyTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
/*---------*
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConnectionIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConnectionIT.java
index 4e06eba0c6c..34d14a7f56b 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConnectionIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreConnectionIT.java
@@ -21,22 +21,39 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.repository.RepositoryConnectionTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
public class ElasticsearchStoreConnectionIT extends RepositoryConnectionTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
public static IsolationLevel[] parameters() {
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreContextIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreContextIT.java
index 5d1ce3862c5..bc1578f5237 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreContextIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreContextIT.java
@@ -17,7 +17,9 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.sail.RDFNotifyingStoreTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
/**
* An extension of RDFStoreTest for testing the class
@@ -26,17 +28,32 @@
public class ElasticsearchStoreContextIT extends RDFNotifyingStoreTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIT.java
index 043d9e82361..e05cd50e28f 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIT.java
@@ -18,7 +18,9 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.sail.RDFNotifyingStoreTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
/**
* An extension of RDFStoreTest for testing the class
@@ -27,17 +29,37 @@
public class ElasticsearchStoreIT extends RDFNotifyingStoreTest {
static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreInterruptIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreInterruptIT.java
index 94dd45b5628..f6f433348c0 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreInterruptIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreInterruptIT.java
@@ -18,7 +18,9 @@
import org.eclipse.rdf4j.testsuite.sail.SailConcurrencyTest;
import org.eclipse.rdf4j.testsuite.sail.SailInterruptTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
/**
* An extension of {@link SailConcurrencyTest} for testing the class
@@ -27,17 +29,32 @@
public class ElasticsearchStoreInterruptIT extends SailInterruptTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIsolationLevelIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIsolationLevelIT.java
index c1397b6cc1a..fd16ce17910 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIsolationLevelIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreIsolationLevelIT.java
@@ -19,7 +19,9 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.sail.SailIsolationLevelTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
/**
* An extension of {@link SailIsolationLevelTest} for testing the class
@@ -28,19 +30,34 @@
public class ElasticsearchStoreIsolationLevelIT extends SailIsolationLevelTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
SailIsolationLevelTest.setUpClass();
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass2() throws Exception {
SailIsolationLevelTest.afterClass();
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreRepositoryIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreRepositoryIT.java
index a7542ab304c..bd204d1da3c 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreRepositoryIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreRepositoryIT.java
@@ -17,23 +17,40 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.repository.RepositoryTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
public class ElasticsearchStoreRepositoryIT extends RepositoryTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlOrderByIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlOrderByIT.java
index 6cc77af2105..6ddbbe33edc 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlOrderByIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlOrderByIT.java
@@ -17,22 +17,39 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.repository.SparqlOrderByTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
public class ElasticsearchStoreSparqlOrderByIT extends SparqlOrderByTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlRegexIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlRegexIT.java
index 3337a7438d6..89e0fb775ec 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlRegexIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreSparqlRegexIT.java
@@ -17,22 +17,39 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.repository.SparqlRegexTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
public class ElasticsearchStoreSparqlRegexIT extends SparqlRegexTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreTupleQueryResultIT.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreTupleQueryResultIT.java
index 227510ab985..d4a725a8d1f 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreTupleQueryResultIT.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/compliance/ElasticsearchStoreTupleQueryResultIT.java
@@ -17,22 +17,39 @@
import org.eclipse.rdf4j.sail.elasticsearchstore.TestHelpers;
import org.eclipse.rdf4j.testsuite.repository.TupleQueryResultTest;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
public class ElasticsearchStoreTupleQueryResultIT extends TupleQueryResultTest {
private static SingletonClientProvider clientPool;
+ private static boolean dockerAvailable;
@BeforeAll
public static void beforeClass() {
- TestHelpers.openClient();
- clientPool = new SingletonClientProvider("localhost", TestHelpers.PORT, TestHelpers.CLUSTER);
+ dockerAvailable = TestHelpers.openClient();
+ if (!dockerAvailable) {
+ return;
+ }
+ clientPool = new SingletonClientProvider(TestHelpers.getHost(), TestHelpers.getTransportPort(),
+ TestHelpers.getClusterName());
+ }
+
+ @BeforeEach
+ public void requireDocker() {
+ Assumptions.assumeTrue(dockerAvailable, "Docker not available for Elasticsearch tests");
}
@AfterAll
public static void afterClass() throws Exception {
- clientPool.close();
- TestHelpers.closeClient();
+ if (clientPool != null) {
+ clientPool.close();
+ clientPool = null;
+ }
+ if (dockerAvailable) {
+ TestHelpers.closeClient();
+ }
}
@Override
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreConfigTest.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreConfigTest.java
index 2f2f1d07dd8..4ae4c57183b 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreConfigTest.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreConfigTest.java
@@ -105,4 +105,4 @@ public void exportAddsAllConfigData() {
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreFactoryTest.java b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreFactoryTest.java
index 520e706dec3..76e8d907a80 100644
--- a/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreFactoryTest.java
+++ b/core/sail/elasticsearch-store/src/test/java/org/eclipse/rdf4j/sail/elasticsearchstore/config/ElasticsearchStoreFactoryTest.java
@@ -66,4 +66,4 @@ private void assertMatchesConfig(ElasticsearchStore sail, ElasticsearchStoreConf
}
-}
+}
\ No newline at end of file
diff --git a/core/sail/elasticsearch/pom.xml b/core/sail/elasticsearch/pom.xml
index db712cdd3b1..3fb7a93ab38 100644
--- a/core/sail/elasticsearch/pom.xml
+++ b/core/sail/elasticsearch/pom.xml
@@ -37,5 +37,58 @@
jcl-over-slf4j
runtime
+
+ co.elastic.clients
+ elasticsearch-java
+ ${elasticsearch.version}
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
+
+ ${project.groupId}
+ rdf4j-sail-memory
+ ${project.version}
+ test
+
+
+ ${project.groupId}
+ rdf4j-repository-sail
+ ${project.version}
+ test
+
+
+
+ org.testcontainers
+ testcontainers
+ 1.19.7
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ 1.19.7
+ test
+
+
+ org.testcontainers
+ elasticsearch
+ 1.19.7
+ test
+
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchDocumentTest.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchDocumentTest.java
new file mode 100644
index 00000000000..b7a51fc0f9e
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchDocumentTest.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.rdf4j.sail.lucene.SearchFields;
+import org.junit.jupiter.api.Test;
+import org.locationtech.spatial4j.context.SpatialContext;
+
+import com.google.common.base.Function;
+
+class ElasticsearchDocumentTest {
+
+ private static final Function GEO = (s) -> SpatialContext.GEO;
+
+ @Test
+ void constructorAndBasicGetters() {
+ ElasticsearchDocument doc = new ElasticsearchDocument(
+ "id1", "resource", "index1", "urn:res", null, GEO);
+
+ assertEquals("id1", doc.getId());
+ assertEquals("resource", doc.getType());
+ assertEquals("index1", doc.getIndex());
+ assertEquals("urn:res", doc.getResource());
+ assertNull(doc.getContext());
+ }
+
+ @Test
+ void addPropertyNameOnly_thenDuplicateFails() {
+ ElasticsearchDocument doc = new ElasticsearchDocument(
+ "id2", "resource", "index2", "urn:r", "urn:c", GEO);
+
+ doc.addProperty("p");
+
+ // Adding again should fail
+ assertThrows(IllegalStateException.class, () -> doc.addProperty("p"));
+
+ assertEquals("urn:c", doc.getContext());
+ assertFalse(doc.hasProperty("p", "v"));
+ assertNull(doc.getProperty("p"));
+ }
+
+ @Test
+ void addPropertyWithText_multipleValuesAggregated() {
+ ElasticsearchDocument doc = new ElasticsearchDocument(
+ "id3", "resource", "index", "urn:res", null, GEO);
+
+ doc.addProperty("name", "Alice");
+ doc.addProperty("name", "Bob");
+
+ List values = doc.getProperty("name");
+ assertNotNull(values);
+ assertEquals(2, values.size());
+ assertTrue(values.contains("Alice"));
+ assertTrue(values.contains("Bob"));
+
+ assertTrue(doc.hasProperty("name", "Alice"));
+ assertFalse(doc.hasProperty("name", "Carol"));
+ }
+
+ @Test
+ void addGeoProperty_pointWkt_addsGeohash() {
+ ElasticsearchDocument doc = new ElasticsearchDocument(
+ "id4", "resource", "index", "urn:res", null, GEO);
+
+ // SpatialContext.GEO understands simple POINT WKT
+ doc.addGeoProperty("loc", "POINT (1 2)");
+
+ String geoPointKey = ElasticsearchIndex.toGeoPointFieldName("loc");
+ Object geohash = doc.getSource().get(geoPointKey);
+ assertNotNull(geohash, "geopoint geohash should be present for POINT WKT");
+ assertTrue(geohash instanceof String);
+ }
+
+ @Test
+ void addGeoProperty_invalidWkt_ignored() {
+ ElasticsearchDocument doc = new ElasticsearchDocument(
+ "id5", "resource", "index", "urn:res", null, GEO);
+
+ // Invalid WKT results in ParseException which must be ignored
+ doc.addGeoProperty("loc", "NOT_A_WKT");
+
+ assertNull(doc.getSource().get(ElasticsearchIndex.toGeoPointFieldName("loc")));
+ assertNull(doc.getSource().get(ElasticsearchIndex.toGeoShapeFieldName("loc")));
+ }
+
+ @Test
+ void getPropertyNamesFromFields() {
+ Map backing = new HashMap<>();
+ // add a few property fields (with encoding of '.')
+ backing.put(ElasticsearchIndex.toPropertyFieldName("name"), "A");
+ backing.put(ElasticsearchIndex.toPropertyFieldName("ex.name"), "B");
+ // non-property fields should be ignored
+ backing.put(SearchFields.URI_FIELD_NAME, "urn:res");
+ backing.put(SearchFields.TEXT_FIELD_NAME, "text");
+ backing.put(SearchFields.CONTEXT_FIELD_NAME, "urn:ctx");
+
+ ElasticsearchDocument doc = new ElasticsearchDocument(
+ "id6", "resource", "index", 1L, 1L, backing, GEO);
+
+ Set names = doc.getPropertyNames();
+ assertEquals(new HashSet<>(Set.of("name", "ex.name")), names);
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexUtilTest.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexUtilTest.java
new file mode 100644
index 00000000000..5b2457d4733
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchIndexUtilTest.java
@@ -0,0 +1,67 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.locationtech.spatial4j.context.SpatialContext;
+
+class ElasticsearchIndexUtilTest {
+
+ @Test
+ void encodeDecodeFieldNameRoundtrip() {
+ String original = "ex.name.with.dots";
+ String encoded = ElasticsearchIndex.encodeFieldName(original);
+ assertEquals("ex^name^with^dots", encoded);
+ String decoded = ElasticsearchIndex.decodeFieldName(encoded);
+ assertEquals(original, decoded);
+ }
+
+ @Test
+ void propertyAndGeoFieldNames() {
+ String prop = "ex.name";
+ String field = ElasticsearchIndex.toPropertyFieldName(prop);
+ assertTrue(field.startsWith(ElasticsearchIndex.PROPERTY_FIELD_PREFIX));
+ assertEquals(prop, ElasticsearchIndex.toPropertyName(field));
+
+ String gp = ElasticsearchIndex.toGeoPointFieldName(prop);
+ assertTrue(gp.startsWith(ElasticsearchIndex.GEOPOINT_FIELD_PREFIX));
+ String gs = ElasticsearchIndex.toGeoShapeFieldName(prop);
+ assertTrue(gs.startsWith(ElasticsearchIndex.GEOSHAPE_FIELD_PREFIX));
+ }
+
+ @Test
+ void createSpatialContextMapperReturnsConstant() {
+ class Exposed extends ElasticsearchIndex {
+ public java.util.function.Function expose(Map p) {
+ return (java.util.function.Function) createSpatialContextMapper(p);
+ }
+ }
+
+ Exposed idx = new Exposed();
+ Map params = new HashMap<>();
+ var fn = idx.expose(params);
+ assertNotNull(fn);
+ SpatialContext a = fn.apply("a");
+ SpatialContext b = fn.apply("b");
+ assertSame(a, b, "mapper should be constant regardless of input property name");
+ }
+
+ @Test
+ void searchNumDocsGuard() {
+ ElasticsearchIndex idx = new ElasticsearchIndex();
+ assertThrows(IllegalArgumentException.class, () -> idx.search(null, null, -2));
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailClearIT.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailClearIT.java
new file mode 100644
index 00000000000..3281250e192
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailClearIT.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.ValueFactory;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.repository.sail.SailRepository;
+import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
+import org.eclipse.rdf4j.sail.lucene.LuceneSail;
+import org.eclipse.rdf4j.sail.memory.MemoryStore;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+@Testcontainers
+class ElasticsearchSailClearIT {
+
+ private static final DockerImageName ES_IMAGE = DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch:7.15.2");
+
+ @Container
+ static final GenericContainer> elastic = new GenericContainer<>(ES_IMAGE)
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("discovery.type", "single-node")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withExposedPorts(9200, 9300)
+ .waitingFor(Wait.forListeningPort());
+
+ private static final ValueFactory VF = SimpleValueFactory.getInstance();
+
+ @BeforeAll
+ static void up() {
+ assertTrue(elastic.isRunning());
+ }
+
+ @AfterAll
+ static void down() {
+ /* handled by Testcontainers */ }
+
+ private static SailRepository newRepository() {
+ String host = elastic.getHost();
+ Integer transport = elastic.getMappedPort(9300);
+
+ LuceneSail lucene = new LuceneSail();
+ lucene.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, "es-it-" + Long.toHexString(System.nanoTime()));
+ lucene.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
+ lucene.setParameter(ElasticsearchIndex.TRANSPORT_KEY, host + ":" + transport);
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_NO_RELOCATING_SHARDS_KEY, "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.ignore_cluster_name",
+ "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.sniff", "false");
+ lucene.setBaseSail(new MemoryStore());
+
+ SailRepository repo = new SailRepository(lucene);
+ repo.init();
+ return repo;
+ }
+
+ private static int matchCount(SailRepository repo, String term) {
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "SELECT ?s WHERE {",
+ " ?s search:matches [",
+ " search:query \"" + term + "\"",
+ " ] .",
+ "}");
+ int count = 0;
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ res.next();
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
+ @Test
+ void clearContextsAndFullClear() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ IRI title = vf.createIRI("http://example.org/title");
+ IRI ctx1 = vf.createIRI("http://example.org/ctx1");
+ IRI ctx2 = vf.createIRI("http://example.org/ctx2");
+
+ IRI s1 = vf.createIRI("http://example.org/s1");
+ IRI s2 = vf.createIRI("http://example.org/s2");
+
+ // index data in two different contexts
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, title, vf.createLiteral("fox fox"), ctx1);
+ cx.add(s2, title, vf.createLiteral("fox fox"), ctx2);
+ cx.commit();
+ }
+
+ assertEquals(2, matchCount(repo, "fox"));
+
+ // clear ctx1 and re-check
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.clear(ctx1);
+ cx.commit();
+ }
+ assertEquals(1, matchCount(repo, "fox"));
+
+ // full clear
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.clear();
+ cx.commit();
+ }
+ assertEquals(0, matchCount(repo, "fox"));
+
+ repo.shutDown();
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailContextGeoIT.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailContextGeoIT.java
new file mode 100644
index 00000000000..9b27a2a6e77
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailContextGeoIT.java
@@ -0,0 +1,141 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.ValueFactory;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.model.vocabulary.GEO;
+import org.eclipse.rdf4j.repository.sail.SailRepository;
+import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
+import org.eclipse.rdf4j.sail.lucene.LuceneSail;
+import org.eclipse.rdf4j.sail.memory.MemoryStore;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+@Testcontainers
+class ElasticsearchSailContextGeoIT {
+
+ private static final DockerImageName ES_IMAGE = DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch:7.15.2");
+
+ @Container
+ static final GenericContainer> elastic = new GenericContainer<>(ES_IMAGE)
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("discovery.type", "single-node")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withExposedPorts(9200, 9300)
+ .waitingFor(Wait.forListeningPort());
+
+ private static final ValueFactory VF = SimpleValueFactory.getInstance();
+
+ @BeforeAll
+ static void up() {
+ assertTrue(elastic.isRunning());
+ }
+
+ @AfterAll
+ static void down() {
+ /* handled by Testcontainers */ }
+
+ private static SailRepository newRepository() {
+ String host = elastic.getHost();
+ Integer transport = elastic.getMappedPort(9300);
+
+ LuceneSail lucene = new LuceneSail();
+ lucene.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, "es-it-" + Long.toHexString(System.nanoTime()));
+ lucene.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
+ lucene.setParameter(ElasticsearchIndex.TRANSPORT_KEY, host + ":" + transport);
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_NO_RELOCATING_SHARDS_KEY, "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.ignore_cluster_name",
+ "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.sniff", "false");
+ lucene.setBaseSail(new MemoryStore());
+
+ SailRepository repo = new SailRepository(lucene);
+ repo.init();
+ return repo;
+ }
+
+ @Test
+ void restrictByGraphContext() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+
+ IRI ctx1 = vf.createIRI("http://example.org/ctx1");
+ IRI ctx2 = vf.createIRI("http://example.org/ctx2");
+ IRI s1 = vf.createIRI("http://example.org/pt1");
+ IRI s2 = vf.createIRI("http://example.org/pt2");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, GEO.AS_WKT, vf.createLiteral("POINT (1 2)", GEO.WKT_LITERAL), ctx1);
+ cx.add(s2, GEO.AS_WKT, vf.createLiteral("POINT (1 2)", GEO.WKT_LITERAL), ctx2);
+ cx.commit();
+ }
+
+ // Query without context restriction returns both
+ String qAll = String.join("\n",
+ "PREFIX geo: ",
+ "PREFIX geof: ",
+ "PREFIX uom: ",
+ "SELECT ?s WHERE {",
+ " ?s geo:asWKT ?w .",
+ " FILTER(geof:distance(\"POINT (1 2)\"^^geo:wktLiteral, ?w, uom:metre) < 1)",
+ "}");
+ List all = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(qAll);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ all.add(res.next().getValue("s").stringValue());
+ }
+ }
+ }
+ assertTrue(all.contains(s1.stringValue()));
+ assertTrue(all.contains(s2.stringValue()));
+
+ // Restrict by GRAPH ctx1 - only s1 should match.
+ String qCtx1 = String.join("\n",
+ "PREFIX geo: ",
+ "PREFIX geof: ",
+ "PREFIX uom: ",
+ "SELECT ?s WHERE {",
+ " GRAPH <" + ctx1 + "> { ?s geo:asWKT ?w . }",
+ " FILTER(geof:distance(\"POINT (1 2)\"^^geo:wktLiteral, ?w, uom:metre) < 1)",
+ "}");
+ List onlyCtx1 = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(qCtx1);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ onlyCtx1.add(res.next().getValue("s").stringValue());
+ }
+ }
+ }
+ assertTrue(onlyCtx1.contains(s1.stringValue()));
+ assertFalse(onlyCtx1.contains(s2.stringValue()));
+
+ repo.shutDown();
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoIT.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoIT.java
new file mode 100644
index 00000000000..15ab4e7a3d6
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailGeoIT.java
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.ValueFactory;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.model.vocabulary.GEO;
+import org.eclipse.rdf4j.repository.sail.SailRepository;
+import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
+import org.eclipse.rdf4j.sail.lucene.LuceneSail;
+import org.eclipse.rdf4j.sail.memory.MemoryStore;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+@Testcontainers
+class ElasticsearchSailGeoIT {
+
+ private static final DockerImageName ES_IMAGE = DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch:7.15.2");
+
+ @Container
+ static final GenericContainer> elastic = new GenericContainer<>(ES_IMAGE)
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("discovery.type", "single-node")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withExposedPorts(9200, 9300)
+ .waitingFor(Wait.forListeningPort());
+
+ private static final ValueFactory VF = SimpleValueFactory.getInstance();
+
+ @BeforeAll
+ static void up() {
+ assertTrue(elastic.isRunning());
+ }
+
+ @AfterAll
+ static void down() {
+ /* @Container lifecycle */ }
+
+ private static SailRepository newRepository() {
+ String host = elastic.getHost();
+ Integer transport = elastic.getMappedPort(9300);
+
+ LuceneSail lucene = new LuceneSail();
+ lucene.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, "es-it-" + Long.toHexString(System.nanoTime()));
+ lucene.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
+ lucene.setParameter(ElasticsearchIndex.TRANSPORT_KEY, host + ":" + transport);
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_NO_RELOCATING_SHARDS_KEY, "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.ignore_cluster_name",
+ "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.sniff", "false");
+ lucene.setBaseSail(new MemoryStore());
+
+ SailRepository repo = new SailRepository(lucene);
+ repo.init();
+ return repo;
+ }
+
+ @Test
+ void withinDistanceMetre_samePointMatches() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ IRI s1 = vf.createIRI("http://example.org/pt1");
+ IRI s2 = vf.createIRI("http://example.org/pt2");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, GEO.AS_WKT, vf.createLiteral("POINT (1 2)", GEO.WKT_LITERAL));
+ cx.add(s2, GEO.AS_WKT, vf.createLiteral("POINT (10 10)", GEO.WKT_LITERAL));
+ cx.commit();
+ }
+
+ String q = String.join("\n",
+ "PREFIX geo: ",
+ "PREFIX geof: ",
+ "PREFIX uom: ",
+ "SELECT ?s ?w WHERE {",
+ " ?s geo:asWKT ?w .",
+ " FILTER(geof:distance(\"POINT (1 2)\"^^geo:wktLiteral, ?w, uom:metre) < 1000)",
+ "}");
+
+ List results = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ var bs = res.next();
+ results.add(bs.getValue("s").stringValue());
+ }
+ }
+ }
+ System.out.println(Arrays.toString(results.toArray()));
+
+ assertTrue(results.contains(s1.stringValue()));
+ assertFalse(results.contains(s2.stringValue()));
+
+ repo.shutDown();
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIT.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIT.java
new file mode 100644
index 00000000000..6eea1d25565
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailIT.java
@@ -0,0 +1,130 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.ValueFactory;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.repository.RepositoryResult;
+import org.eclipse.rdf4j.repository.sail.SailRepository;
+import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
+import org.eclipse.rdf4j.sail.lucene.LuceneSail;
+import org.eclipse.rdf4j.sail.memory.MemoryStore;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+@Testcontainers
+class ElasticsearchSailIT {
+
+ private static final DockerImageName ES_IMAGE = DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch:7.15.2");
+
+ @Container
+ static final GenericContainer> elastic = new GenericContainer<>(ES_IMAGE)
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("discovery.type", "single-node")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withExposedPorts(9200, 9300)
+ .waitingFor(Wait.forListeningPort());
+
+ private static final ValueFactory VF = SimpleValueFactory.getInstance();
+ private static final IRI EX_LABEL = VF.createIRI("http://example.org/label");
+
+ @BeforeAll
+ static void checkRunning() {
+ assertTrue(elastic.isRunning(), "Elasticsearch testcontainer must be running");
+ }
+
+ @AfterAll
+ static void stop() {
+ // container is stopped automatically by @Container lifecycle
+ }
+
+ @Test
+ void indexAndSearchByProperty() throws Exception {
+ // Arrange: create a LuceneSail backed by ElasticsearchIndex
+ String host = elastic.getHost();
+ Integer transport = elastic.getMappedPort(9300);
+
+ LuceneSail lucene = new LuceneSail();
+ lucene.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, "es-it-" + Long.toHexString(System.nanoTime()));
+ lucene.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
+ // provide ES Transport address (host:port)
+ lucene.setParameter(ElasticsearchIndex.TRANSPORT_KEY, host + ":" + transport);
+ // be lenient about cluster name matching/sniffing in tests
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_NO_RELOCATING_SHARDS_KEY, "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.ignore_cluster_name",
+ "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.sniff", "false");
+
+ MemoryStore base = new MemoryStore();
+ lucene.setBaseSail(base);
+
+ SailRepository repo = new SailRepository(lucene);
+ repo.init();
+
+ ValueFactory vf = repo.getValueFactory();
+ IRI exS = vf.createIRI("http://example.org/s");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(exS, EX_LABEL, vf.createLiteral("The quick brown fox jumps"));
+ cx.add(vf.createIRI("http://example.org/t2"), EX_LABEL, vf.createLiteral("A lazy dog"));
+ cx.commit();
+ }
+
+ // Act: run a LuceneSail search over the property with snippet/score
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "PREFIX ex: ",
+ "SELECT ?s ?score ?snip WHERE {",
+ " ?s search:matches [",
+ " search:property ex:label ;",
+ " search:query \"quick\" ;",
+ " search:score ?score ;",
+ " search:snippet ?snip",
+ " ] .",
+ "}");
+
+ List subjects = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ var bs = res.next();
+ subjects.add(bs.getValue("s").stringValue());
+ // score should exist and be numeric
+ assertNotNull(bs.getValue("score"));
+ // snippet is optional but should appear for matches
+ assertNotNull(bs.getValue("snip"));
+ }
+ }
+ }
+
+ // Assert: the subject with the 'quick' literal is returned
+ assertTrue(subjects.contains(exS.stringValue()), "Expected match for subject with 'quick'");
+
+ // Cleanup
+ repo.shutDown();
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTextIT.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTextIT.java
new file mode 100644
index 00000000000..c5f0befb738
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSailTextIT.java
@@ -0,0 +1,323 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.rdf4j.model.IRI;
+import org.eclipse.rdf4j.model.ValueFactory;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.repository.sail.SailRepository;
+import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection;
+import org.eclipse.rdf4j.sail.lucene.LuceneSail;
+import org.eclipse.rdf4j.sail.lucene.SearchFields;
+import org.eclipse.rdf4j.sail.memory.MemoryStore;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.strategy.Wait;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+@Testcontainers
+class ElasticsearchSailTextIT {
+
+ private static final DockerImageName ES_IMAGE = DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch:7.15.2");
+
+ @Container
+ static final GenericContainer> elastic = new GenericContainer<>(ES_IMAGE)
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("discovery.type", "single-node")
+ .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m")
+ .withExposedPorts(9200, 9300)
+ .waitingFor(Wait.forListeningPort());
+
+ private static final ValueFactory VF = SimpleValueFactory.getInstance();
+ private static final IRI EX_TITLE = VF.createIRI("http://example.org/title");
+ private static final IRI EX_COMMENT = VF.createIRI("http://example.org/comment");
+
+ @BeforeAll
+ static void up() {
+ assertTrue(elastic.isRunning());
+ }
+
+ @AfterAll
+ static void down() {
+ // handled by @Container
+ }
+
+ private static SailRepository newRepository() {
+ String host = elastic.getHost();
+ Integer transport = elastic.getMappedPort(9300);
+
+ LuceneSail lucene = new LuceneSail();
+ lucene.setParameter(ElasticsearchIndex.INDEX_NAME_KEY, "es-it-" + Long.toHexString(System.nanoTime()));
+ lucene.setParameter(LuceneSail.INDEX_CLASS_KEY, ElasticsearchIndex.class.getName());
+ lucene.setParameter(ElasticsearchIndex.TRANSPORT_KEY, host + ":" + transport);
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_STATUS_KEY, "yellow");
+ lucene.setParameter(ElasticsearchIndex.WAIT_FOR_NO_RELOCATING_SHARDS_KEY, "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.ignore_cluster_name",
+ "true");
+ lucene.setParameter(ElasticsearchIndex.ELASTICSEARCH_KEY_PREFIX + "client.transport.sniff", "false");
+ lucene.setBaseSail(new MemoryStore());
+
+ SailRepository repo = new SailRepository(lucene);
+ repo.init();
+ return repo;
+ }
+
+ @Test
+ void highlightAcrossAllProperties() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ IRI s1 = vf.createIRI("http://example.org/s1");
+ IRI s2 = vf.createIRI("http://example.org/s2");
+ IRI s3 = vf.createIRI("http://example.org/s3");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, EX_TITLE, vf.createLiteral("The quick brown fox jumps"));
+ cx.add(s1, EX_COMMENT, vf.createLiteral("Over the lazy dog"));
+ cx.add(s2, EX_TITLE, vf.createLiteral("Some other text"));
+ cx.add(s3, EX_COMMENT, vf.createLiteral("quick again appears here"));
+ cx.commit();
+ }
+
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "SELECT ?s ?snip WHERE {",
+ " ?s search:matches [",
+ " search:query \"quick\" ;",
+ " search:snippet ?snip",
+ " ] .",
+ "}");
+
+ List snippets = new ArrayList<>();
+ List results = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ var bs = res.next();
+ results.add(bs.getValue("s").stringValue());
+ snippets.add(bs.getValue("snip").stringValue());
+ }
+ }
+ }
+
+ assertTrue(results.contains(s1.stringValue()));
+ assertTrue(results.contains(s3.stringValue()));
+ assertFalse(results.contains(s2.stringValue()));
+ assertTrue(snippets.stream().anyMatch(s -> s.contains(SearchFields.HIGHLIGHTER_PRE_TAG)));
+
+ repo.shutDown();
+ }
+
+ @Test
+ void limitNumDocs() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ for (int i = 0; i < 3; i++) {
+ IRI s = vf.createIRI("http://example.org/r" + i);
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s, EX_TITLE, vf.createLiteral("fox fox fox"));
+ cx.commit();
+ }
+ }
+
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "SELECT ?s WHERE {",
+ " ?s search:matches [",
+ " search:query \"fox\" ;",
+ " search:numDocs \"1\"",
+ " ] .",
+ "}");
+
+ int count;
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ count = 0;
+ while (res.hasNext()) {
+ res.next();
+ count++;
+ }
+ }
+ }
+
+ assertEquals(1, count, "Expected query to limit results to 1");
+ repo.shutDown();
+ }
+
+ @Test
+ void propertyVarBindingAndSnippet() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ IRI s1 = vf.createIRI("http://example.org/s1");
+ IRI s2 = vf.createIRI("http://example.org/s2");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, EX_TITLE, vf.createLiteral("quick fox"));
+ cx.add(s2, EX_COMMENT, vf.createLiteral("quick dog"));
+ cx.commit();
+ }
+
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "PREFIX ex: ",
+ "SELECT ?s ?p ?snip WHERE {",
+ " ?s search:matches [",
+ " search:query \"quick\" ;",
+ " search:property ?p ;",
+ " search:snippet ?snip",
+ " ] .",
+ "}");
+
+ List props = new ArrayList<>();
+ List subjects = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ var bs = res.next();
+ subjects.add(bs.getValue("s").stringValue());
+ props.add(bs.getValue("p").stringValue());
+ }
+ }
+ }
+
+ assertTrue(subjects.contains(s1.stringValue()));
+ assertTrue(subjects.contains(s2.stringValue()));
+ assertTrue(props.contains(EX_TITLE.stringValue()));
+ assertTrue(props.contains(EX_COMMENT.stringValue()));
+
+ repo.shutDown();
+ }
+
+ @Test
+ void propertyFilterReturnsOnlyMatchingProperty() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ IRI s1 = vf.createIRI("http://example.org/s1");
+ IRI s2 = vf.createIRI("http://example.org/s2");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, EX_TITLE, vf.createLiteral("contains quick"));
+ cx.add(s2, EX_COMMENT, vf.createLiteral("contains quick"));
+ cx.commit();
+ }
+
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "PREFIX ex: ",
+ "SELECT ?s WHERE {",
+ " ?s search:matches [",
+ " search:property ex:title ;",
+ " search:query \"quick\"",
+ " ] .",
+ "}");
+
+ List results = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ results.add(res.next().getValue("s").stringValue());
+ }
+ }
+ }
+
+ assertTrue(results.contains(s1.stringValue()));
+ assertFalse(results.contains(s2.stringValue()));
+
+ repo.shutDown();
+ }
+
+ @Test
+ void multiParamQueryUnsupported() {
+ SailRepository repo = newRepository();
+
+ // Two search:query statements inside same matches node -> ElasticsearchIndex should reject
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "SELECT ?s WHERE {",
+ " ?s search:matches [",
+ " search:query \"a\" ;",
+ " search:query \"b\"",
+ " ] .",
+ "}");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ assertThrows(Exception.class, () -> {
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ res.next();
+ }
+ }
+ });
+ }
+
+ repo.shutDown();
+ }
+
+ @Test
+ void textQueryRestrictedByGraphContext() {
+ SailRepository repo = newRepository();
+ ValueFactory vf = repo.getValueFactory();
+ IRI s1 = vf.createIRI("http://example.org/s1");
+ IRI s2 = vf.createIRI("http://example.org/s2");
+ IRI ctx1 = vf.createIRI("http://example.org/ctx1");
+ IRI ctx2 = vf.createIRI("http://example.org/ctx2");
+
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ cx.begin();
+ cx.add(s1, EX_TITLE, vf.createLiteral("quick term"), ctx1);
+ cx.add(s2, EX_TITLE, vf.createLiteral("quick term"), ctx2);
+ cx.commit();
+ }
+
+ String q = String.join("\n",
+ "PREFIX search: ",
+ "PREFIX ex: ",
+ "SELECT ?s WHERE {",
+ " GRAPH <" + ctx1 + "> { ?s ex:title ?o . }",
+ " ?s search:matches [",
+ " search:property ex:title ;",
+ " search:query \"quick\"",
+ " ] .",
+ "}");
+
+ List results = new ArrayList<>();
+ try (SailRepositoryConnection cx = repo.getConnection()) {
+ var tq = cx.prepareTupleQuery(q);
+ try (var res = tq.evaluate()) {
+ while (res.hasNext()) {
+ results.add(res.next().getValue("s").stringValue());
+ }
+ }
+ }
+
+ assertTrue(results.contains(s1.stringValue()));
+ assertFalse(results.contains(s2.stringValue()));
+ repo.shutDown();
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSpatialSupportTest.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSpatialSupportTest.java
new file mode 100644
index 00000000000..173a399c903
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/ElasticsearchSpatialSupportTest.java
@@ -0,0 +1,46 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.junit.jupiter.api.Test;
+import org.locationtech.spatial4j.context.SpatialContext;
+import org.locationtech.spatial4j.shape.Point;
+import org.locationtech.spatial4j.shape.Shape;
+
+class ElasticsearchSpatialSupportTest {
+
+ @Test
+ void defaultSpatialSupportThrowsForShapes() {
+ ElasticsearchSpatialSupport support = invokeGetSpatialSupport();
+ assertNotNull(support);
+
+ Shape s = SpatialContext.GEO.makePoint(1, 2);
+ assertTrue(s instanceof Point);
+
+ assertThrows(UnsupportedOperationException.class, () -> callToGeoJSON(support, s));
+ assertThrows(UnsupportedOperationException.class, () -> callToShapeBuilder(support, s));
+ }
+
+ // Access package-private/static methods via helpers in same package
+ private static ElasticsearchSpatialSupport invokeGetSpatialSupport() {
+ return ElasticsearchSpatialSupport.getSpatialSupport();
+ }
+
+ private static void callToShapeBuilder(ElasticsearchSpatialSupport support, Shape s) {
+ support.toShapeBuilder(s);
+ }
+
+ private static void callToGeoJSON(ElasticsearchSpatialSupport support, Shape s) {
+ support.toGeoJSON(s);
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/config/ElasticsearchSailFactoryTest.java b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/config/ElasticsearchSailFactoryTest.java
new file mode 100644
index 00000000000..69b20cec3ea
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/java/org/eclipse/rdf4j/sail/elasticsearch/config/ElasticsearchSailFactoryTest.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.rdf4j.sail.elasticsearch.config;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.eclipse.rdf4j.sail.Sail;
+import org.eclipse.rdf4j.sail.config.AbstractSailImplConfig;
+import org.eclipse.rdf4j.sail.config.SailConfigException;
+import org.eclipse.rdf4j.sail.lucene.LuceneSail;
+import org.junit.jupiter.api.Test;
+
+class ElasticsearchSailFactoryTest {
+
+ @Test
+ void getSailType() {
+ ElasticsearchSailFactory f = new ElasticsearchSailFactory();
+ assertEquals(ElasticsearchSailFactory.SAIL_TYPE, f.getSailType());
+ }
+
+ @Test
+ void getSailFromConfig() throws Exception {
+ ElasticsearchSailFactory f = new ElasticsearchSailFactory();
+ ElasticsearchSailConfig cfg = new ElasticsearchSailConfig("./idx");
+ assertEquals(ElasticsearchSailFactory.SAIL_TYPE, cfg.getType());
+
+ Sail sail = f.getSail(cfg);
+ assertNotNull(sail);
+ assertTrue(sail instanceof LuceneSail);
+ }
+
+ @Test
+ void wrongTypeThrows() {
+ ElasticsearchSailFactory f = new ElasticsearchSailFactory();
+ AbstractSailImplConfig wrong = new AbstractSailImplConfig("wrong:type") {
+ };
+ assertThrows(SailConfigException.class, () -> f.getSail(wrong));
+ }
+}
diff --git a/core/sail/elasticsearch/src/test/resources/logback-test.xml b/core/sail/elasticsearch/src/test/resources/logback-test.xml
new file mode 100644
index 00000000000..d653d318176
--- /dev/null
+++ b/core/sail/elasticsearch/src/test/resources/logback-test.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ %d %green([%thread]) %highlight(%level) %logger{50} - %msg%n
+
+
+
+
+
+
+
+
+
+
+