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 + + + + + + + + + + +