Repository: quarkusio/quarkus
Issue: ClassLoader can't find resource since 2.7.0

### Describe the bug

Hi,

After updating from 2.6.3-Final to 2.7.0-FInal, I've been trying to load a certificate file with property:
`quarkus.http.ssl.certificate.file=META-INF/certs/server.pem` (I know it's deprecated)
but it ended with a "FileNotFoundException". 

After digging a bit, using this property then calls `Thread.currentThread().getContextClassLoader().getResourceAsStream(path);` in VertxHttpRecorder.java

I used this same code in a simple OnStart and observed the same behaviour.

I tried to dig further using debgger but only found that getResourceAsStream() from QuarkusClassLoader.java behaviour differs :
         - 2.6.3 : 'elements' property contains [MemoryClassPathElement, DirectoryClassPathElement]
         - 2.7.0 : 'elements' property contains [MemoryClassPathElement, PathTreeClassPathElement]



 

### Expected behavior

File's loaded 

### Actual behavior

File's not found, FileNotFoundException

### How to Reproduce?

1 - Download default zip from https://code.quarkus.io/ 

2 - Add class
```
@ApplicationScoped
public class AppLifecycleBean {

    void onStart(@Observes StartupEvent ev) {
        // Method invoked by VertxHttpRecorder to initialize property "quarkus.http.ssl.certificate.file"
        // Works in 2.6.3 but null in 2.7.0
        var pathValue = "META-INF/certs/server.pem";
        var path = Path.of(pathValue);
        var inputStreamNullResult = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.toString());

        // Works with any version using String directly
        // Maybe something to do with Windows path somewhere ?
        var inputStreamOkResult = Thread.currentThread().getContextClassLoader().getResourceAsStream(pathValue);

        /* Note that might help digging: using debugger during getResourceAsStream() in QuarkusClassLoader.java:
         - 2.6.3 : 'elements' property contains [MemoryClassPathElement, DirectoryClassPathElement]
         - 2.7.0 : 'elements' property contains [MemoryClassPathElement, PathTreeClassPathElement]
         */
    }

}
```

3 -  Add any file in resources directory, "META-INF/certs/server.pem"

4 - Switch from version 2.6.3-Final and 2.7.0-Final to observe that `inputStream` is null in 2.7.0-Final.

### Output of `uname -a` or `ver`

Windows 10

### Output of `java -version`

openjdk 11.0.10 2021-01-19

### GraalVM version (if different from Java)

_No response_

### Quarkus version or git rev

_No response_

### Build tool (ie. output of `mvnw --version` or `gradlew --version`)

Apache Maven 3.8.4

### Additional information

_No response_

Changed files:
extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
extensions/spring-cloud-config-client/runtime/src/main/java/io/quarkus/spring/cloud/config/client/runtime/VertxSpringCloudConfigGateway.java
extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
core/runtime/src/main/java/io/quarkus/runtime/util/ClassPathUtils.java
extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java
extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java

diff --git a/core/runtime/src/main/java/io/quarkus/runtime/util/ClassPathUtils.java b/core/runtime/src/main/java/io/quarkus/runtime/util/ClassPathUtils.java
index 3782c79581b..c8301db988f 100644
--- a/core/runtime/src/main/java/io/quarkus/runtime/util/ClassPathUtils.java
+++ b/core/runtime/src/main/java/io/quarkus/runtime/util/ClassPathUtils.java
@@ -22,6 +22,23 @@ public class ClassPathUtils {
     private static final String FILE = "file";
     private static final String JAR = "jar";
 
+    /**
+     * Translates a file system-specific path to a Java classpath resource name
+     * that uses '/' as a separator.
+     * 
+     * @param path file system path
+     * @return Java classpath resource name
+     */
+    public static String toResourceName(Path path) {
+        if (path == null) {
+            return null;
+        }
+        if (path.getFileSystem().getSeparator().equals("/")) {
+            return path.toString();
+        }
+        return path.toString().replace(path.getFileSystem().getSeparator(), "/");
+    }
+
     /**
      * Invokes {@link #consumeAsStreams(ClassLoader, String, Consumer)} passing in
      * an instance of the current thread's context classloader as the classloader
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
index 47673ac5e57..a26af11d2f5 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/GrpcSslUtils.java
@@ -13,6 +13,7 @@
 
 import io.quarkus.grpc.runtime.config.GrpcServerConfiguration;
 import io.quarkus.grpc.runtime.config.SslServerConfig;
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.vertx.core.buffer.Buffer;
 import io.vertx.core.http.HttpServerOptions;
 import io.vertx.core.http.HttpVersion;
@@ -126,7 +127,7 @@ static boolean applySslOptions(GrpcServerConfiguration config, HttpServerOptions
     private static byte[] getFileContent(Path path) throws IOException {
         byte[] data;
         final InputStream resource = Thread.currentThread().getContextClassLoader()
-                .getResourceAsStream(path.toString());
+                .getResourceAsStream(ClassPathUtils.toResourceName(path));
         if (resource != null) {
             try (InputStream is = resource) {
                 data = doRead(is);
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
index 4d974f74e17..9704fab557f 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/supports/Channels.java
@@ -42,6 +42,7 @@
 import io.quarkus.grpc.runtime.config.GrpcServerConfiguration;
 import io.quarkus.grpc.runtime.config.SslClientConfig;
 import io.quarkus.runtime.LaunchMode;
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.smallrye.stork.Stork;
 
 @SuppressWarnings({ "OptionalIsPresent", "Convert2Lambda" })
@@ -223,7 +224,7 @@ private static GrpcClientConfiguration testConfig(GrpcServerConfiguration server
 
     private static InputStream streamFor(Path path, String resourceName) {
         final InputStream resource = Thread.currentThread().getContextClassLoader()
-                .getResourceAsStream(path.toString());
+                .getResourceAsStream(ClassPathUtils.toResourceName(path));
         if (resource != null) {
             return resource;
         } else {
diff --git a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java
index 8c73ab2b1ad..28c847fa573 100644
--- a/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java
+++ b/extensions/infinispan-client/deployment/src/main/java/io/quarkus/infinispan/client/deployment/InfinispanClientProcessor.java
@@ -61,7 +61,7 @@ class InfinispanClientProcessor {
     private static final Log log = LogFactory.getLog(InfinispanClientProcessor.class);
 
     private static final String META_INF = "META-INF";
-    private static final String HOTROD_CLIENT_PROPERTIES = META_INF + File.separator + "hotrod-client.properties";
+    private static final String HOTROD_CLIENT_PROPERTIES = "hotrod-client.properties";
     private static final String PROTO_EXTENSION = ".proto";
     private static final String SASL_SECURITY_PROVIDER = "com.sun.security.sasl.Provider";
 
@@ -85,13 +85,14 @@ InfinispanPropertiesBuildItem setup(ApplicationArchivesBuildItem applicationArch
         feature.produce(new FeatureBuildItem(Feature.INFINISPAN_CLIENT));
         additionalBeans.produce(AdditionalBeanBuildItem.unremovableOf(InfinispanClientProducer.class));
         systemProperties.produce(new SystemPropertyBuildItem("io.netty.noUnsafe", "true"));
-        hotDeployment.produce(new HotDeploymentWatchedFileBuildItem(HOTROD_CLIENT_PROPERTIES));
+        hotDeployment.produce(new HotDeploymentWatchedFileBuildItem(META_INF + File.separator + HOTROD_CLIENT_PROPERTIES));
 
         // Enable SSL support by default
         sslNativeSupport.produce(new ExtensionSslNativeSupportBuildItem(Feature.INFINISPAN_CLIENT));
         nativeImageSecurityProviders.produce(new NativeImageSecurityProviderBuildItem(SASL_SECURITY_PROVIDER));
 
-        InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(HOTROD_CLIENT_PROPERTIES);
+        InputStream stream = Thread.currentThread().getContextClassLoader()
+                .getResourceAsStream(META_INF + "/" + HOTROD_CLIENT_PROPERTIES);
         Properties properties;
         if (stream == null) {
             properties = new Properties();
diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
index e92b7881ffb..744dcc9e6e1 100644
--- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
+++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java
@@ -32,6 +32,7 @@
 import io.quarkus.oidc.common.runtime.OidcCommonConfig.Tls.Verification;
 import io.quarkus.runtime.TlsConfig;
 import io.quarkus.runtime.configuration.ConfigurationException;
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.smallrye.jwt.algorithm.SignatureAlgorithm;
 import io.smallrye.jwt.build.Jwt;
 import io.smallrye.jwt.build.JwtSignatureBuilder;
@@ -401,7 +402,8 @@ public static Uni<JsonObject> discoverMetadata(WebClient client, String authServ
 
     private static byte[] getFileContent(Path path) throws IOException {
         byte[] data;
-        final InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.toString());
+        final InputStream resource = Thread.currentThread().getContextClassLoader()
+                .getResourceAsStream(ClassPathUtils.toResourceName(path));
         if (resource != null) {
             try (InputStream is = resource) {
                 data = doRead(is);
diff --git a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
index e2a6e0bbc44..b719ad95ec6 100644
--- a/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
+++ b/extensions/smallrye-openapi/deployment/src/main/java/io/quarkus/smallrye/openapi/deployment/SmallRyeOpenApiProcessor.java
@@ -65,11 +65,11 @@
 import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
 import io.quarkus.deployment.logging.LogCleanupFilterBuildItem;
 import io.quarkus.deployment.pkg.builditem.OutputTargetBuildItem;
-import io.quarkus.paths.PathUtils;
 import io.quarkus.resteasy.common.spi.ResteasyDotNames;
 import io.quarkus.resteasy.server.common.spi.AllowedJaxRsAnnotationPrefixBuildItem;
 import io.quarkus.resteasy.server.common.spi.ResteasyJaxrsConfigBuildItem;
 import io.quarkus.runtime.LaunchMode;
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig;
 import io.quarkus.smallrye.openapi.deployment.filter.AutoRolesAllowedFilter;
 import io.quarkus.smallrye.openapi.deployment.filter.AutoTagFilter;
@@ -172,7 +172,7 @@ void configFiles(BuildProducer<HotDeploymentWatchedFileBuildItem> watchedFiles,
             List<Path> additionalStaticDocuments = openApiConfig.additionalDocsDirectory.get();
             for (Path path : additionalStaticDocuments) {
                 // Scan all yaml and json files
-                List<String> filesInDir = getResourceFiles(PathUtils.asString(path, "/"),
+                List<String> filesInDir = getResourceFiles(ClassPathUtils.toResourceName(path),
                         outputTargetBuildItem.getOutputDirectory());
                 for (String possibleFile : filesInDir) {
                     watchedFiles.produce(new HotDeploymentWatchedFileBuildItem(possibleFile));
@@ -795,7 +795,7 @@ private List<Result> findStaticModels(SmallRyeOpenApiConfig openApiConfig, Path
             for (Path path : additionalStaticDocuments) {
                 // Scan all yaml and json files
                 try {
-                    List<String> filesInDir = getResourceFiles(PathUtils.asString(path, "/"), target);
+                    List<String> filesInDir = getResourceFiles(ClassPathUtils.toResourceName(path), target);
                     for (String possibleModelFile : filesInDir) {
                         addStaticModelIfExist(results, possibleModelFile);
                     }
diff --git a/extensions/spring-cloud-config-client/runtime/src/main/java/io/quarkus/spring/cloud/config/client/runtime/VertxSpringCloudConfigGateway.java b/extensions/spring-cloud-config-client/runtime/src/main/java/io/quarkus/spring/cloud/config/client/runtime/VertxSpringCloudConfigGateway.java
index 081a525469e..5b08792f7ab 100644
--- a/extensions/spring-cloud-config-client/runtime/src/main/java/io/quarkus/spring/cloud/config/client/runtime/VertxSpringCloudConfigGateway.java
+++ b/extensions/spring-cloud-config-client/runtime/src/main/java/io/quarkus/spring/cloud/config/client/runtime/VertxSpringCloudConfigGateway.java
@@ -19,6 +19,7 @@
 import com.fasterxml.jackson.databind.ObjectMapper;
 
 import io.quarkus.runtime.TlsConfig;
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.smallrye.mutiny.Uni;
 import io.vertx.core.net.JksOptions;
 import io.vertx.core.net.KeyStoreOptionsBase;
@@ -130,7 +131,7 @@ private static String determineStoreType(Path keyStorePath) {
     private static byte[] storeBytes(Path keyStorePath)
             throws Exception {
         InputStream classPathResource = Thread.currentThread().getContextClassLoader()
-                .getResourceAsStream(keyStorePath.toString());
+                .getResourceAsStream(ClassPathUtils.toResourceName(keyStorePath));
         if (classPathResource != null) {
             try (InputStream is = classPathResource) {
                 return allBytes(is);
diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
index b6385ef98a3..e800959e190 100644
--- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
+++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/VertxHttpRecorder.java
@@ -59,6 +59,7 @@
 import io.quarkus.runtime.configuration.ConfigurationException;
 import io.quarkus.runtime.configuration.MemorySize;
 import io.quarkus.runtime.shutdown.ShutdownConfig;
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.quarkus.vertx.core.runtime.VertxCoreRecorder;
 import io.quarkus.vertx.core.runtime.config.VertxConfiguration;
 import io.quarkus.vertx.http.runtime.HttpConfiguration.InsecureRequests;
@@ -762,7 +763,8 @@ private static KeyStoreOptions createKeyStoreOptions(Path path, String password,
 
     private static byte[] getFileContent(Path path) throws IOException {
         byte[] data;
-        final InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.toString());
+        final InputStream resource = Thread.currentThread().getContextClassLoader()
+                .getResourceAsStream(ClassPathUtils.toResourceName(path));
         if (resource != null) {
             try (InputStream is = resource) {
                 data = doRead(is);
diff --git a/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java b/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java
index 1e42e7fde2f..f60e9b3ec12 100644
--- a/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java
+++ b/integration-tests/bouncycastle-fips-jsse/src/test/java/io/quarkus/it/bouncycastle/BouncyCastleFipsJsseTestCase.java
@@ -23,6 +23,7 @@
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import io.quarkus.runtime.util.ClassPathUtils;
 import io.quarkus.test.common.http.TestHTTPResource;
 import io.quarkus.test.junit.QuarkusTest;
 import io.vertx.core.Vertx;
@@ -130,7 +131,8 @@ public void run() throws Throwable {
 
     private static byte[] getFileContent(Path path) throws IOException {
         byte[] data;
-        final InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.toString());
+        final InputStream resource = Thread.currentThread().getContextClassLoader()
+                .getResourceAsStream(ClassPathUtils.toResourceName(path));
         if (resource != null) {
             try (InputStream is = resource) {
                 data = doRead(is);