CASSANDRASC-66 Fix builds in Apache CI

Currently, tests running under {{org.apache.cassandra.sidecar.HealthServiceSslTest}}
[are failing](https://ci-cassandra.apache.org/job/cassandra~sidecar/42/) when running
inside ASF's CI. Logs are showing that some resources (keystore and truststore) are
not found. This is causing the tests to fail.

In this commit, we read the resource from the stream, which is guaranteed to exist
as long as the resource exists and the resource name is correct, then we write the
resource to a temporary directory, and use the file name to set the keystore and
truststore path as part of the configuration options.

patch by Francisco Guerrero; reviewed by Dinesh Joshi, Yifan Cai for CASSANDRASC-66
diff --git a/CHANGES.txt b/CHANGES.txt
index 8e5826f..9cdb549 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,5 +1,6 @@
 1.0.0
 -----
+ * Fix failing unit tests in Apache CI (CASSANDRASC-66)
  * Support credential rotation in JmxClient (CASSANDRASC-63)
  * File descriptor is not being closed on MD5 checksum (CASSANDRASC-64)
  * Expose JMX host and port from JMXClient (CASSANDRASC-59)
diff --git a/common/build.gradle b/common/build.gradle
index dd9fad1..cd8e5f0 100644
--- a/common/build.gradle
+++ b/common/build.gradle
@@ -17,6 +17,8 @@
  * under the License.
  */
 
+import java.nio.file.Paths
+
 plugins {
     id 'java-library'
     id "java-test-fixtures"
@@ -39,6 +41,13 @@
 
 test {
     useJUnitPlatform()
+    reports {
+        junitXml.enabled = true
+        def destDir = Paths.get(rootProject.rootDir.absolutePath, "build", "test-results", "common").toFile()
+        println("Destination directory for common tests: ${destDir}")
+        junitXml.destination = destDir
+        html.enabled = true
+    }
 }
 
 dependencies {
@@ -56,6 +65,8 @@
     testImplementation("org.junit.jupiter:junit-jupiter-params:${project.junitVersion}")
     testImplementation(group: 'io.netty', name: 'netty-codec-http', version: '4.1.69.Final')
     testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${project.junitVersion}")
+
+    testFixturesImplementation("org.assertj:assertj-core:3.24.2")
 }
 
 java {
diff --git a/common/src/main/java/org/apache/cassandra/sidecar/common/JmxClient.java b/common/src/main/java/org/apache/cassandra/sidecar/common/JmxClient.java
index d5cdcf3..2b37514 100644
--- a/common/src/main/java/org/apache/cassandra/sidecar/common/JmxClient.java
+++ b/common/src/main/java/org/apache/cassandra/sidecar/common/JmxClient.java
@@ -96,6 +96,15 @@
         this(jmxServiceURL, () -> role, () -> password, () -> false);
     }
 
+    public JmxClient(String host,
+                     int port,
+                     Supplier<String> roleSupplier,
+                     Supplier<String> passwordSupplier,
+                     BooleanSupplier enableSslSupplier)
+    {
+        this(buildJmxServiceURL(host, port), roleSupplier, passwordSupplier, enableSslSupplier);
+    }
+
     public JmxClient(JMXServiceURL jmxServiceURL,
                      Supplier<String> roleSupplier,
                      Supplier<String> passwordSupplier,
diff --git a/common/src/test/java/org/apache/cassandra/sidecar/common/JmxClientTest.java b/common/src/test/java/org/apache/cassandra/sidecar/common/JmxClientTest.java
index e3269f6..6e37100 100644
--- a/common/src/test/java/org/apache/cassandra/sidecar/common/JmxClientTest.java
+++ b/common/src/test/java/org/apache/cassandra/sidecar/common/JmxClientTest.java
@@ -21,6 +21,8 @@
 import java.io.IOException;
 import java.lang.management.ManagementFactory;
 import java.net.MalformedURLException;
+import java.net.ServerSocket;
+import java.nio.file.Path;
 import java.rmi.registry.LocateRegistry;
 import java.rmi.registry.Registry;
 import java.rmi.server.UnicastRemoteObject;
@@ -30,7 +32,6 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.BooleanSupplier;
@@ -47,6 +48,7 @@
 import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.junit.platform.commons.util.Preconditions;
 
 import org.apache.cassandra.sidecar.common.exceptions.JmxAuthenticationException;
@@ -65,7 +67,7 @@
  */
 public class JmxClientTest
 {
-
+    private static final int port;
     private static final JMXServiceURL serviceURL;
     private static final String objectName = "org.apache.cassandra.jmx:type=ExtendedImport";
     public static final int PROXIES_TO_TEST = 10_000;
@@ -73,17 +75,22 @@
     private static JMXConnectorServer jmxServer;
     private static MBeanServer mbs;
     private static Registry registry;
+    @TempDir
+    private static Path passwordFilePath;
 
     @BeforeAll
     public static void setUp() throws Exception
     {
+        System.setProperty("java.rmi.server.hostname", "127.0.0.1");
         System.setProperty("java.rmi.server.randomIds", "true");
-        String passwordFile = Objects.requireNonNull(JmxClientTest.class
-                                                     .getClassLoader()
-                                                     .getResource("testJmxPassword.properties")).getPath();
+        String passwordFile = ResourceUtils.writeResourceToPath(JmxClientTest.class.getClassLoader(),
+                                                                passwordFilePath,
+                                                                "testJmxPassword.properties")
+                                           .toAbsolutePath()
+                                           .toString();
         Map<String, String> env = new HashMap<>();
         env.put("jmx.remote.x.password.file", passwordFile);
-        registry = LocateRegistry.createRegistry(9999);
+        registry = LocateRegistry.createRegistry(port);
         mbs = ManagementFactory.getPlatformMBeanServer();
         jmxServer = JMXConnectorServerFactory.newJMXConnectorServer(serviceURL, env, mbs);
         jmxServer.start();
@@ -194,24 +201,6 @@
                            new JmxClient(serviceURL, () -> "controlRole", () -> "password", enableSslSupplier));
     }
 
-    private static void testSupplierThrows(String errorMessage, JmxClient jmxClient) throws IOException
-    {
-        try (JmxClient client = jmxClient)
-        {
-            assertThatExceptionOfType(JmxAuthenticationException.class)
-            .isThrownBy(() ->
-                        client.proxy(Import.class, objectName)
-                              .importNewSSTables(Sets.newHashSet("foo", "bar"),
-                                                 true,
-                                                 true,
-                                                 true,
-                                                 true,
-                                                 true,
-                                                 true))
-            .withMessageContaining(errorMessage);
-        }
-    }
-
     @Test
     public void testRetryAfterAuthenticationFailureWithCorrectCredentials() throws IOException
     {
@@ -293,6 +282,19 @@
         }
     }
 
+    @Test
+    public void testConstructorWithHostPort() throws IOException
+    {
+        try (JmxClient client = new JmxClient("127.0.0.1", port, () -> "controlRole", () -> "password", () -> false))
+        {
+            List<String> result = client.proxy(Import.class, objectName)
+                                        .importNewSSTables(Sets.newHashSet("foo", "bar"), true,
+                                                           true, true, true, true,
+                                                           true);
+            assertThat(result.size()).isEqualTo(0);
+        }
+    }
+
     /**
      * Simulates to C*'s `nodetool import` call
      */
@@ -344,15 +346,47 @@
         }
     }
 
+    private static void testSupplierThrows(String errorMessage, JmxClient jmxClient) throws IOException
+    {
+        try (JmxClient client = jmxClient)
+        {
+            assertThatExceptionOfType(JmxAuthenticationException.class)
+            .isThrownBy(() ->
+                        client.proxy(Import.class, objectName)
+                              .importNewSSTables(Sets.newHashSet("foo", "bar"),
+                                                 true,
+                                                 true,
+                                                 true,
+                                                 true,
+                                                 true,
+                                                 true))
+            .withMessageContaining(errorMessage);
+        }
+    }
+
     static
     {
         try
         {
-            serviceURL = new JMXServiceURL("service:jmx:rmi://localhost/jndi/rmi://localhost:9999/jmxrmi");
+            port = availablePort();
+            serviceURL = new JMXServiceURL("service:jmx:rmi://127.0.0.1:" + port
+                                           + "/jndi/rmi://127.0.0.1:" + port + "/jmxrmi");
         }
         catch (MalformedURLException e)
         {
             throw new RuntimeException(e);
         }
     }
+
+    private static int availablePort()
+    {
+        try (ServerSocket socket = new ServerSocket(0))
+        {
+            return socket.getLocalPort();
+        }
+        catch (IOException exception)
+        {
+            return 9999;
+        }
+    }
 }
diff --git a/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/ResourceUtils.java b/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/ResourceUtils.java
new file mode 100644
index 0000000..c731002
--- /dev/null
+++ b/common/src/testFixtures/java/org/apache/cassandra/sidecar/common/ResourceUtils.java
@@ -0,0 +1,74 @@
+/*
+ * 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.cassandra.sidecar.common;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+
+/**
+ * A utilities class to handle resources for tests
+ */
+public final class ResourceUtils
+{
+    /**
+     * Writes a resource with {@code resourceName} loaded from the {@link ClassLoader classLoader} into the
+     * {@code destinationPath}
+     *
+     * @param classLoader     the class loader for the resource
+     * @param destinationPath the destination path to write the file
+     * @param resourceName    the name of the resource to be loaded
+     * @return the {@link Path} to the created resource
+     */
+    public static Path writeResourceToPath(ClassLoader classLoader, Path destinationPath, String resourceName)
+    {
+        try
+        {
+            Path resourcePath = destinationPath.resolve(resourceName);
+
+            // ensure parent directory is created
+            Files.createDirectories(resourcePath.getParent());
+
+            try (InputStream inputStream = classLoader.getResourceAsStream(resourceName);
+                 OutputStream outputStream = Files.newOutputStream(resourcePath))
+            {
+                assertThat(inputStream).isNotNull();
+
+                int length;
+                byte[] buffer = new byte[1024];
+                while ((length = inputStream.read(buffer)) != -1)
+                {
+                    outputStream.write(buffer, 0, length);
+                }
+            }
+            return resourcePath;
+        }
+        catch (IOException exception)
+        {
+            String failureMessage = "Unable to create resource " + resourceName;
+            fail(failureMessage, exception);
+            throw new RuntimeException(failureMessage, exception);
+        }
+    }
+}
diff --git a/gradlew.bat b/gradlew.bat
deleted file mode 100644
index 0f8d593..0000000
--- a/gradlew.bat
+++ /dev/null
@@ -1,84 +0,0 @@
-@if "%DEBUG%" == "" @echo off

-@rem ##########################################################################

-@rem

-@rem  Gradle startup script for Windows

-@rem

-@rem ##########################################################################

-

-@rem Set local scope for the variables with windows NT shell

-if "%OS%"=="Windows_NT" setlocal

-

-set DIRNAME=%~dp0

-if "%DIRNAME%" == "" set DIRNAME=.

-set APP_BASE_NAME=%~n0

-set APP_HOME=%DIRNAME%

-

-@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.

-set DEFAULT_JVM_OPTS="-Xmx64m"

-

-@rem Find java.exe

-if defined JAVA_HOME goto findJavaFromJavaHome

-

-set JAVA_EXE=java.exe

-%JAVA_EXE% -version >NUL 2>&1

-if "%ERRORLEVEL%" == "0" goto init

-

-echo.

-echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:findJavaFromJavaHome

-set JAVA_HOME=%JAVA_HOME:"=%

-set JAVA_EXE=%JAVA_HOME%/bin/java.exe

-

-if exist "%JAVA_EXE%" goto init

-

-echo.

-echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%

-echo.

-echo Please set the JAVA_HOME variable in your environment to match the

-echo location of your Java installation.

-

-goto fail

-

-:init

-@rem Get command-line arguments, handling Windows variants

-

-if not "%OS%" == "Windows_NT" goto win9xME_args

-

-:win9xME_args

-@rem Slurp the command line arguments.

-set CMD_LINE_ARGS=

-set _SKIP=2

-

-:win9xME_args_slurp

-if "x%~1" == "x" goto execute

-

-set CMD_LINE_ARGS=%*

-

-:execute

-@rem Setup the command line

-

-set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

-

-@rem Execute Gradle

-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%

-

-:end

-@rem End local scope for the variables with windows NT shell

-if "%ERRORLEVEL%"=="0" goto mainEnd

-

-:fail

-rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of

-rem the _cmd.exe /c_ return code!

-if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1

-exit /b 1

-

-:mainEnd

-if "%OS%"=="Windows_NT" endlocal

-

-:omega

diff --git a/scripts/.mvn/wrapper/maven-wrapper.jar b/scripts/.mvn/wrapper/maven-wrapper.jar
new file mode 100644
index 0000000..cb28b0e
--- /dev/null
+++ b/scripts/.mvn/wrapper/maven-wrapper.jar
Binary files differ
diff --git a/scripts/.mvn/wrapper/maven-wrapper.properties b/scripts/.mvn/wrapper/maven-wrapper.properties
new file mode 100644
index 0000000..70f4f50
--- /dev/null
+++ b/scripts/.mvn/wrapper/maven-wrapper.properties
@@ -0,0 +1,18 @@
+# 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.
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.8/apache-maven-3.8.8-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
diff --git a/scripts/build-dtest-jars.sh b/scripts/build-dtest-jars.sh
index 62e7857..80b40cc 100755
--- a/scripts/build-dtest-jars.sh
+++ b/scripts/build-dtest-jars.sh
@@ -19,22 +19,27 @@
 
 set -xe
 BRANCHES=${BRANCHES:-cassandra-4.0 trunk}
-REPO=${REPO:-"git@github.com:apache/cassandra.git"}
+REPO=${REPO:-"https://github.com/apache/cassandra.git"}
 SCRIPT_DIR=$( dirname -- "$( readlink -f -- "$0"; )"; )
-DTEST_JAR_DIR="$(dirname ${SCRIPT_DIR}/)/dtest-jars"
+DTEST_JAR_DIR="$(dirname "${SCRIPT_DIR}/")/dtest-jars"
 BUILD_DIR="${DTEST_JAR_DIR}/build"
-mkdir -p $BUILD_DIR
+mkdir -p "${BUILD_DIR}"
+
+# host key verification
+mkdir -p ~/.ssh
+ssh-keyscan github.com >> ~/.ssh/known_hosts
+
 for branch in $BRANCHES; do
-  cd ${BUILD_DIR}
+  cd "${BUILD_DIR}"
   # check out the correct cassandra version:
-  if [ ! -d ${branch} ] ; then
-    git clone --depth 1 --single-branch --branch $branch $REPO $branch
-    cd $branch
+  if [ ! -d "${branch}" ] ; then
+    git clone --depth 1 --single-branch --branch "${branch}" "${REPO}" "${branch}"
+    cd "${branch}"
   else
-    cd $branch
+    cd "${branch}"
     git pull
   fi
-  git checkout $branch
+  git checkout "${branch}"
   git clean -fd
   CASSANDRA_VERSION=$(cat build.xml | grep 'property name="base.version"' | awk -F "\"" '{print $4}')
   # Loop to prevent failure due to maven-ant-tasks not downloading a jar.
@@ -43,7 +48,7 @@
           RETURN="0"
           break
         else
-          ${SCRIPT_DIR}/build-shaded-dtest-jar-local.sh
+          "${SCRIPT_DIR}/build-shaded-dtest-jar-local.sh"
           RETURN="$?"
           if [ "${RETURN}" -eq "0" ]; then
               break
@@ -55,4 +60,4 @@
       echo "Build failed with exit code: ${RETURN}"
       exit ${RETURN}
   fi
-done
\ No newline at end of file
+done
diff --git a/scripts/build-shaded-dtest-jar-local.sh b/scripts/build-shaded-dtest-jar-local.sh
index 7be43c8..8ab2791 100755
--- a/scripts/build-shaded-dtest-jar-local.sh
+++ b/scripts/build-shaded-dtest-jar-local.sh
@@ -25,33 +25,33 @@
 CASSANDRA_VERSION=$(cat build.xml | grep 'property name="base.version"' | awk -F "\"" '{print $4}')
 GIT_HASH=$(git rev-parse --short HEAD)
 DTEST_ARTIFACT_ID=${ARTIFACT_NAME}-local
-DTEST_JAR_DIR="$(dirname ${SCRIPT_DIR}/)/dtest-jars"
+DTEST_JAR_DIR="$(dirname "${SCRIPT_DIR}/")/dtest-jars"
 
-echo $CASSANDRA_VERSION
-echo $GIT_HASH
-echo $DTEST_ARTIFACT_ID
+echo "${CASSANDRA_VERSION}"
+echo "${GIT_HASH}"
+echo "${DTEST_ARTIFACT_ID}"
 
 ant clean
 ant dtest-jar -Dno-checkstyle=true
 
 # Install the version that will be shaded
-mvn install:install-file               \
-   -Dfile=./build/dtest-${CASSANDRA_VERSION}.jar \
-   -DgroupId=org.apache.cassandra      \
-   -DartifactId=${DTEST_ARTIFACT_ID} \
-   -Dversion=${CASSANDRA_VERSION}-${GIT_HASH}         \
-   -Dpackaging=jar                     \
-   -DgeneratePom=true                  \
-   -DlocalRepositoryPath=${REPO_DIR}
+"${SCRIPT_DIR}/mvnw" install:install-file                            \
+                     -Dfile="./build/dtest-${CASSANDRA_VERSION}.jar" \
+                     -DgroupId=org.apache.cassandra                  \
+                     -DartifactId="${DTEST_ARTIFACT_ID}"             \
+                     -Dversion="${CASSANDRA_VERSION}-${GIT_HASH}"    \
+                     -Dpackaging=jar                                 \
+                     -DgeneratePom=true                              \
+                     -DlocalRepositoryPath="${REPO_DIR}"
 
 # Create shaded artifact
-mvn -f ${SCRIPT_DIR}/relocate-dtest-dependencies.pom package \
-    -Drevision=${CASSANDRA_VERSION} \
-    -DskipTests \
-    -Ddtest.version=${CASSANDRA_VERSION}-${GIT_HASH} \
-    -Dmaven.repo.local=${REPO_DIR} \
-    -DoutputFilePath=${DTEST_JAR_DIR}/dtest-${CASSANDRA_VERSION}.jar \
-    -Drelocation.prefix=shaded-${GIT_HASH} \
-    -nsu -U
+"${SCRIPT_DIR}/mvnw" --file "${SCRIPT_DIR}/relocate-dtest-dependencies.pom" package     \
+                     -Drevision="${CASSANDRA_VERSION}"                                  \
+                     -DskipTests                                                        \
+                     -Ddtest.version="${CASSANDRA_VERSION}-${GIT_HASH}"                 \
+                     -Dmaven.repo.local="${REPO_DIR}"                                   \
+                     -DoutputFilePath="${DTEST_JAR_DIR}/dtest-${CASSANDRA_VERSION}.jar" \
+                     -Drelocation.prefix="shaded-${GIT_HASH}"                           \
+                     --no-snapshot-updates --update-snapshots
 
 set +xe
diff --git a/scripts/mvnw b/scripts/mvnw
new file mode 100755
index 0000000..8d937f4
--- /dev/null
+++ b/scripts/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# 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.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+#   JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+#   MAVEN_OPTS - parameters passed to the Java VM when running Maven
+#     e.g. to debug Maven itself, use
+#       set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+#   MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+  if [ -f /usr/local/etc/mavenrc ] ; then
+    . /usr/local/etc/mavenrc
+  fi
+
+  if [ -f /etc/mavenrc ] ; then
+    . /etc/mavenrc
+  fi
+
+  if [ -f "$HOME/.mavenrc" ] ; then
+    . "$HOME/.mavenrc"
+  fi
+
+fi
+
+# OS specific support.  $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+  CYGWIN*) cygwin=true ;;
+  MINGW*) mingw=true;;
+  Darwin*) darwin=true
+    # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+    # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+    if [ -z "$JAVA_HOME" ]; then
+      if [ -x "/usr/libexec/java_home" ]; then
+        JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+      else
+        JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+      fi
+    fi
+    ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+  if [ -r /etc/gentoo-release ] ; then
+    JAVA_HOME=$(java-config --jre-home)
+  fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+  [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+    JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+  javaExecutable="$(which javac)"
+  if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+    # readlink(1) is not available as standard on Solaris 10.
+    readLink=$(which readlink)
+    if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+      if $darwin ; then
+        javaHome="$(dirname "\"$javaExecutable\"")"
+        javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+      else
+        javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+      fi
+      javaHome="$(dirname "\"$javaExecutable\"")"
+      javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+      JAVA_HOME="$javaHome"
+      export JAVA_HOME
+    fi
+  fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+  if [ -n "$JAVA_HOME"  ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+      # IBM's JDK on AIX uses strange locations for the executables
+      JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+      JAVACMD="$JAVA_HOME/bin/java"
+    fi
+  else
+    JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+  fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+  echo "Error: JAVA_HOME is not defined correctly." >&2
+  echo "  We cannot execute $JAVACMD" >&2
+  exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+  echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+  if [ -z "$1" ]
+  then
+    echo "Path not specified to find_maven_basedir"
+    return 1
+  fi
+
+  basedir="$1"
+  wdir="$1"
+  while [ "$wdir" != '/' ] ; do
+    if [ -d "$wdir"/.mvn ] ; then
+      basedir=$wdir
+      break
+    fi
+    # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+    if [ -d "${wdir}" ]; then
+      wdir=$(cd "$wdir/.." || exit 1; pwd)
+    fi
+    # end of workaround
+  done
+  printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+  if [ -f "$1" ]; then
+    # Remove \r in case we run on Windows within Git Bash
+    # and check out the repository with auto CRLF management
+    # enabled. Otherwise, we may read lines that are delimited with
+    # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+    # splitting rules.
+    tr -s '\r\n' ' ' < "$1"
+  fi
+}
+
+log() {
+  if [ "$MVNW_VERBOSE" = true ]; then
+    printf '%s\n' "$1"
+  fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+  exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+    log "Found $wrapperJarPath"
+else
+    log "Couldn't find $wrapperJarPath, downloading it ..."
+
+    if [ -n "$MVNW_REPOURL" ]; then
+      wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    else
+      wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+    fi
+    while IFS="=" read -r key value; do
+      # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+      safeValue=$(echo "$value" | tr -d '\r')
+      case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+      esac
+    done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+    log "Downloading from: $wrapperUrl"
+
+    if $cygwin; then
+      wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+    fi
+
+    if command -v wget > /dev/null; then
+        log "Found wget ... using wget"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        else
+            wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+        fi
+    elif command -v curl > /dev/null; then
+        log "Found curl ... using curl"
+        [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+        if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+            curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        else
+            curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+        fi
+    else
+        log "Falling back to using Java to download"
+        javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+        javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+        # For Cygwin, switch paths to Windows format before running javac
+        if $cygwin; then
+          javaSource=$(cygpath --path --windows "$javaSource")
+          javaClass=$(cygpath --path --windows "$javaClass")
+        fi
+        if [ -e "$javaSource" ]; then
+            if [ ! -e "$javaClass" ]; then
+                log " - Compiling MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/javac" "$javaSource")
+            fi
+            if [ -e "$javaClass" ]; then
+                log " - Running MavenWrapperDownloader.java ..."
+                ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+            fi
+        fi
+    fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+  case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+  esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+  wrapperSha256Result=false
+  if command -v sha256sum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  elif command -v shasum > /dev/null; then
+    if echo "$wrapperSha256Sum  $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+      wrapperSha256Result=true
+    fi
+  else
+    echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+    echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+    exit 1
+  fi
+  if [ $wrapperSha256Result = false ]; then
+    echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+    echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+    echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+    exit 1
+  fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+  [ -n "$JAVA_HOME" ] &&
+    JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+  [ -n "$CLASSPATH" ] &&
+    CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+  [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+    MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+  $MAVEN_OPTS \
+  $MAVEN_DEBUG_OPTS \
+  -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+  "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+  ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml
index 46f0f39..9394a11 100644
--- a/spotbugs-exclude.xml
+++ b/spotbugs-exclude.xml
@@ -78,4 +78,9 @@
         <Class name="org.apache.cassandra.sidecar.client.HttpResponseImpl" />
     </Match>
 
+    <Match>
+        <Bug pattern="NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE" />
+        <Class name="org.apache.cassandra.sidecar.common.ResourceUtils" />
+    </Match>
+
 </FindBugsFilter>
diff --git a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
index ba01bc9..e56f477 100644
--- a/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
+++ b/src/test/java/org/apache/cassandra/sidecar/AbstractHealthServiceTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.cassandra.sidecar;
 
+import java.nio.file.Path;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -25,6 +26,7 @@
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.DisplayName;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -51,6 +53,9 @@
 public abstract class AbstractHealthServiceTest
 {
     private static final Logger logger = LoggerFactory.getLogger(AbstractHealthServiceTest.class);
+
+    @TempDir
+    private Path certPath;
     private Vertx vertx;
     private HttpServer server;
 
@@ -59,7 +64,7 @@
     public AbstractModule testModule()
     {
         if (isSslEnabled())
-            return new TestSslModule();
+            return new TestSslModule(certPath);
 
         return new TestModule();
     }
diff --git a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
index 5e39e83..bef0208 100644
--- a/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
+++ b/src/test/java/org/apache/cassandra/sidecar/TestSslModule.java
@@ -19,7 +19,7 @@
 package org.apache.cassandra.sidecar;
 
 import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.file.Path;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -28,31 +28,37 @@
 import org.apache.cassandra.sidecar.config.CacheConfiguration;
 import org.apache.cassandra.sidecar.config.WorkerPoolConfiguration;
 
+import static org.apache.cassandra.sidecar.common.ResourceUtils.writeResourceToPath;
+
+
 /**
  * Changes to the TestModule to define SSL dependencies
  */
 public class TestSslModule extends TestModule
 {
     private static final Logger logger = LoggerFactory.getLogger(TestSslModule.class);
+    private final Path certPath;
+
+    public TestSslModule(Path certPath)
+    {
+        this.certPath = certPath;
+    }
 
     @Override
     public Configuration abstractConfig(InstancesConfig instancesConfig)
     {
-        String keyStorePath = TestSslModule.class.getClassLoader()
-                                                 .getResource("certs/test.p12")
-                                                 .getPath();
+        ClassLoader classLoader = TestSslModule.class.getClassLoader();
+        Path keyStorePath = writeResourceToPath(classLoader, certPath, "certs/test.p12");
         String keyStorePassword = "password";
 
-        String trustStorePath = TestSslModule.class.getClassLoader()
-                                                         .getResource("certs/ca.p12")
-                                                         .getPath();
+        Path trustStorePath = writeResourceToPath(classLoader, certPath, "certs/ca.p12");
         String trustStorePassword = "password";
 
-        if (!Files.exists(Paths.get(keyStorePath)))
+        if (!Files.exists(keyStorePath))
         {
             logger.error("JMX password file not found in path={}", keyStorePath);
         }
-        if (!Files.exists(Paths.get(trustStorePath)))
+        if (!Files.exists(trustStorePath))
         {
             logger.error("Trust Store file not found in path={}", trustStorePath);
         }
@@ -65,9 +71,9 @@
                .setHost("127.0.0.1")
                .setPort(6475)
                .setHealthCheckFrequency(1000)
-               .setKeyStorePath(keyStorePath)
+               .setKeyStorePath(keyStorePath.toAbsolutePath().toString())
                .setKeyStorePassword(keyStorePassword)
-               .setTrustStorePath(trustStorePath)
+               .setTrustStorePath(trustStorePath.toAbsolutePath().toString())
                .setTrustStorePassword(trustStorePassword)
                .setSslEnabled(true)
                .setRateLimitStreamRequestsPerSecond(1)
diff --git a/vertx-client/build.gradle b/vertx-client/build.gradle
index 9f6fdd3..37933ed 100644
--- a/vertx-client/build.gradle
+++ b/vertx-client/build.gradle
@@ -16,6 +16,8 @@
  * limitations under the License.
  */
 
+import java.nio.file.Paths
+
 plugins {
     id('java-library')
     id('idea')
@@ -33,6 +35,13 @@
 
 test {
     useJUnitPlatform()
+    reports {
+        junitXml.enabled = true
+        def destDir = Paths.get(rootProject.rootDir.absolutePath, "build", "test-results", "vertx-client").toFile()
+        println("Destination directory for vertx-client tests: ${destDir}")
+        junitXml.destination = destDir
+        html.enabled = true
+    }
 }
 
 configurations {