This is an automated email from the ASF dual-hosted git repository.

baedke pushed a commit to branch issue/oak-11364
in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git


The following commit(s) were added to refs/heads/issue/oak-11364 by this push:
     new 57a609fce1 OAK-11364: For oak-jcr tests, support automatic 
starting/stopping of docker containers used as RDB servers for RDBDocumentStore
57a609fce1 is described below

commit 57a609fce10f4f20427bd0602e1d99fbca4360dd
Author: Manfred Baedke <manfred.bae...@gmail.com>
AuthorDate: Wed Jan 8 15:52:09 2025 +0100

    OAK-11364: For oak-jcr tests, support automatic starting/stopping of docker 
containers used as RDB servers for RDBDocumentStore
    
    done
---
 .../oak/jcr/OakDocumentRDBRepositoryStub.java      |  12 +-
 .../jackrabbit/oak/fixture/DocumentRdbFixture.java |  13 +-
 .../jackrabbit/oak/plugins/document/RdbUtils.java  |  75 ++++++++++
 .../oak/plugins/document/rdb/RdbDockerRule.java    | 165 +++++++++++++++++++++
 4 files changed, 248 insertions(+), 17 deletions(-)

diff --git 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
index 8f17b3b04e..3d95a311b9 100644
--- 
a/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
+++ 
b/oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/OakDocumentRDBRepositoryStub.java
@@ -26,6 +26,7 @@ import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
 
 import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory;
 import 
org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
@@ -35,16 +36,11 @@ import 
org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
  */
 public class OakDocumentRDBRepositoryStub extends BaseRepositoryStub {
 
-    protected static final String URL = System.getProperty("rdb.jdbc-url", 
"jdbc:h2:file:./{fname}oaktest;DB_CLOSE_ON_EXIT=FALSE");
-
-    protected static final String USERNAME = 
System.getProperty("rdb.jdbc-user", "sa");
-
-    protected static final String PASSWD = 
System.getProperty("rdb.jdbc-passwd", "");
 
     private final Repository repository;
 
     private static final String fname = (new File("target")).isDirectory() ? 
"target/" : "";
-    private static final String jdbcUrl = URL.replace("{fname}", fname);
+    private static final String jdbcUrl = 
RdbUtils.mapJdbcURL().replace("{fname}", fname);
 
     /**
      * Constructor as required by the JCR TCK.
@@ -64,7 +60,7 @@ public class OakDocumentRDBRepositoryStub extends 
BaseRepositoryStub {
             m = new RDBDocumentNodeStoreBuilder().
                     memoryCacheSize(64 * 1024 * 1024).
                     setPersistentCache("target/persistentCache,time").
-                    setRDBConnection(RDBDataSourceFactory.forJdbcUrl(jdbcUrl, 
USERNAME, PASSWD), options).
+                    setRDBConnection(RDBDataSourceFactory.forJdbcUrl(jdbcUrl, 
RdbUtils.USERNAME, RdbUtils.PASSWD), options).
                     build();
             Jcr jcr = new Jcr(m);
             preCreateRepository(jcr);
@@ -83,7 +79,7 @@ public class OakDocumentRDBRepositoryStub extends 
BaseRepositoryStub {
 
     public static boolean isAvailable() {
         try {
-            Connection c = 
DriverManager.getConnection(OakDocumentRDBRepositoryStub.jdbcUrl, USERNAME, 
PASSWD);
+            Connection c = 
DriverManager.getConnection(OakDocumentRDBRepositoryStub.jdbcUrl, 
RdbUtils.USERNAME, RdbUtils.PASSWD);
             c.close();
             return true;
         }
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
index e3f00f4770..a261f53b5d 100644
--- 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/fixture/DocumentRdbFixture.java
@@ -29,6 +29,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import javax.sql.DataSource;
 
 import org.apache.jackrabbit.oak.plugins.document.DocumentNodeStore;
+import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBDataSourceFactory;
 import 
org.apache.jackrabbit.oak.plugins.document.rdb.RDBDocumentNodeStoreBuilder;
 import org.apache.jackrabbit.oak.plugins.document.rdb.RDBOptions;
@@ -44,18 +45,12 @@ public class DocumentRdbFixture extends NodeStoreFixture {
 
     private final String fname = (new File("target")).isDirectory() ? 
"target/" : "";
 
-    private final String pUrl = System.getProperty("rdb.jdbc-url", 
"jdbc:h2:file:./{fname}oaktest");
-
-    private final String pUser = System.getProperty("rdb.jdbc-user", "sa");
-
-    private final String pPasswd = System.getProperty("rdb.jdbc-passwd", "");
-
     @Override
     public NodeStore createNodeStore() {
         String prefix = "T" + Long.toHexString(System.currentTimeMillis());
         RDBOptions options = new 
RDBOptions().tablePrefix(prefix).dropTablesOnClose(true);
-        this.jdbcUrl = pUrl.replace("{fname}", fname);
-        DataSource ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl, pUser, 
pPasswd);
+        this.jdbcUrl = RdbUtils.mapJdbcURL().replace("{fname}", fname);
+        DataSource ds = RDBDataSourceFactory.forJdbcUrl(jdbcUrl, 
RdbUtils.USERNAME, RdbUtils.PASSWD);
         //do not reuse the whiteboard
         setWhiteboard(new DefaultWhiteboard());
         RDBDocumentNodeStoreBuilder builder = new 
RDBDocumentNodeStoreBuilder();
@@ -83,6 +78,6 @@ public class DocumentRdbFixture extends NodeStoreFixture {
 
     @Override
     public String toString() {
-        return "DocumentNodeStore[RDB] on " + Objects.toString(this.jdbcUrl, 
this.pUrl);
+        return "DocumentNodeStore[RDB] on " + Objects.toString(this.jdbcUrl);
     }
 }
\ No newline at end of file
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/RdbUtils.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/RdbUtils.java
new file mode 100755
index 0000000000..3c3fc55340
--- /dev/null
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/RdbUtils.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document;
+
+import org.apache.jackrabbit.oak.plugins.document.rdb.RdbDockerRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RdbUtils {
+
+    private static final Logger LOG = LoggerFactory.getLogger(RdbUtils.class);
+
+    public static final String URL = System.getProperty("rdb.jdbc-url", 
"jdbc:h2:file:./{fname}oaktest;DB_CLOSE_ON_EXIT=FALSE");
+    public static final String USERNAME = System.getProperty("rdb.jdbc-user", 
"sa");
+    public static final String PASSWD = System.getProperty("rdb.jdbc-passwd", 
"");
+    public static final String IMAGE = System.getProperty("rdb.docker-image", 
"");
+
+    private static AtomicInteger port = new AtomicInteger(-1);
+    private static AtomicReference<String> host = new 
AtomicReference<>("localhost");
+
+    static {
+        try {
+            if (RdbDockerRule.isDockerImageAvailable()) {
+                RdbDockerRule rule = new RdbDockerRule();
+                rule.apply(new Statement() {
+                    @Override
+                    public void evaluate() {
+                        port.set(rule.getExposedPort());
+                    }
+                }, Description.EMPTY).evaluate();
+            }
+        } catch (Throwable t) {
+            LOG.debug("Failed to initialize docker container", t);
+        }
+    }
+
+    public static String mapJdbcURL() {
+        return mapJdbcURL(URL);
+    }
+
+    public static String mapJdbcURL(String jdbcURL) {
+        if (port.get() > -1) {
+            String normalizedJdbcUri = jdbcURL.replaceFirst("@//", 
"//").replaceFirst("@", "//");
+            Pattern pattern = Pattern.compile("//[^:/]+(:(\\d+))?");
+            Matcher matcher = pattern.matcher(normalizedJdbcUri);
+            if (matcher.find()) {
+                if (matcher.groupCount() > 1) {
+                    return matcher.replaceFirst("//" + host + ":" + port);
+                }
+            }
+        }
+        return jdbcURL;
+    }
+}
diff --git 
a/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RdbDockerRule.java
 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RdbDockerRule.java
new file mode 100644
index 0000000000..0480e2adc5
--- /dev/null
+++ 
b/oak-store-document/src/test/java/org/apache/jackrabbit/oak/plugins/document/rdb/RdbDockerRule.java
@@ -0,0 +1,165 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.document.rdb;
+
+import org.apache.jackrabbit.guava.common.base.Strings;
+import org.apache.jackrabbit.oak.plugins.document.RdbUtils;
+import org.junit.Assume;
+import org.junit.rules.ExternalResource;
+import org.junit.runner.Description;
+import org.junit.runners.model.MultipleFailureException;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.images.RemoteDockerImage;
+import org.testcontainers.utility.DockerImageName;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+
+/**
+ * A MongoDB {@link GenericContainer}.
+ */
+public class RdbDockerRule extends ExternalResource {
+
+    private static final Logger LOG = 
LoggerFactory.getLogger(RdbDockerRule.class);
+
+    private static final AtomicReference<Exception> STARTUP_EXCEPTION = new 
AtomicReference<>();
+    private static final boolean RDB_AVAILABLE;
+    private GenericContainer<?> rdbContainer;
+
+    private static DockerImageName IMAGE = null;
+    private static int exposedPort = getPortFromJdbcURL(RdbUtils.URL);
+
+    static {
+        if (!Strings.isNullOrEmpty(RdbUtils.IMAGE)) {
+            IMAGE = DockerImageName.parse(RdbUtils.IMAGE);
+        }
+        boolean dockerAvailable = false;
+        boolean imageAvailable = false;
+        try {
+            dockerAvailable = checkDockerAvailability();
+            if (dockerAvailable) {
+                imageAvailable = checkImageAvailability();
+            } else {
+                LOG.info("docker not available");
+            }
+        } catch (Throwable t) {
+            LOG.error("not able to pull specified docker image: {}, error: ", 
RdbUtils.IMAGE, t);
+        }
+        RDB_AVAILABLE = dockerAvailable && imageAvailable;
+    }
+
+    @Override
+    protected void before() throws Throwable {
+        if (!RDB_AVAILABLE || rdbContainer != null && 
rdbContainer.isRunning()) {
+            return;
+        }
+        rdbContainer = new GenericContainer<>(IMAGE)
+                .withExposedPorts(exposedPort)
+                .withStartupTimeout(Duration.ofMinutes(5));
+
+        try {
+            long startTime = Instant.now().toEpochMilli();
+            rdbContainer.start();
+            LOG.info("RDB container started in: " + 
(Instant.now().toEpochMilli() - startTime) + " ms");
+        } catch (Exception e) {
+            LOG.error("error while starting RDB container, error: ", e);
+            STARTUP_EXCEPTION.set(e);
+            throw e;
+        }
+    }
+
+    @Override
+    public Statement apply(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                try {
+                    before();
+                } catch (Throwable e) {
+                    Assume.assumeNoException(STARTUP_EXCEPTION.get());
+                    throw e;
+                }
+
+                List<Throwable> errors = new ArrayList<>();
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    errors.add(t);
+                }
+                MultipleFailureException.assertEmpty(errors);
+            }
+        };
+    }
+
+    private static boolean checkImageAvailability() throws TimeoutException {
+        if (Strings.isNullOrEmpty(RdbUtils.IMAGE)) {
+            return false;
+        }
+        RemoteDockerImage remoteDockerImage = new RemoteDockerImage(IMAGE);
+        remoteDockerImage.get(1, TimeUnit.MINUTES);
+        return true;
+    }
+
+    private static boolean checkDockerAvailability() {
+        return DockerClientFactory.instance().isDockerAvailable();
+    }
+
+    public static boolean isDockerImageAvailable() {
+        return RDB_AVAILABLE;
+    }
+
+    public int getExposedPort() {
+        return exposedPort;
+    }
+
+    public String getHost() {
+        return rdbContainer.getHost();
+    }
+
+    public int getMappedPort() {
+        return rdbContainer.getMappedPort(exposedPort);
+    }
+
+    public static int getPortFromJdbcURL(String jdbcURL) {
+        String normalizedJdbcUri = jdbcURL.replaceFirst("@//", 
"//").replaceFirst("@", "//");
+        Pattern pattern = Pattern.compile("//[^:/]+(:(\\d+))?");
+        Matcher matcher = pattern.matcher(normalizedJdbcUri);
+        if (matcher.find()) {
+            if (matcher.groupCount() > 1) {
+                try {
+                    return Integer.parseInt(matcher.group(2));
+                } catch (NumberFormatException ignored) {
+                    //should not happen
+                }
+            }
+        }
+        return -1;
+    }
+
+}

Reply via email to