You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by fm...@apache.org on 2026/05/04 17:38:43 UTC

(camel) branch main updated: CAMEL-23370: Fix PackageScanJarResource losing subdirectory paths in JARs

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

Croway pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 178b0aee0bed CAMEL-23370: Fix PackageScanJarResource losing subdirectory paths in JARs
178b0aee0bed is described below

commit 178b0aee0bed837ff4db0d230ea6ae71d74cd7a1
Author: Croway <fe...@gmail.com>
AuthorDate: Mon May 4 18:30:51 2026 +0200

    CAMEL-23370: Fix PackageScanJarResource losing subdirectory paths in JARs
    
    Replace URLClassLoader-based stream opening in getInputStream() with
    direct jar: URL resolution via URLConnection. The URLClassLoader field
    was a resource leak (never closed) and its getResourceAsStream()
    behavior with leading-/ paths is platform-dependent. The new approach
    builds the full jar: URL from the stored location and opens it directly,
    which is more reliable and avoids JAR file locking on Windows.
    
    Add a test that creates a temporary JAR with resources in subdirectories,
    scans with classpath wildcard, and verifies all resources are found with
    working InputStreams.
---
 .../DefaultPackageScanResourceResolverTest.java    | 56 ++++++++++++++++++++++
 .../camel/support/scan/PackageScanJarResource.java | 15 +++---
 2 files changed, 65 insertions(+), 6 deletions(-)

diff --git a/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolverTest.java b/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolverTest.java
index 927df0d163d9..ff23af47adbe 100644
--- a/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolverTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/impl/engine/DefaultPackageScanResourceResolverTest.java
@@ -17,11 +17,22 @@
 package org.apache.camel.impl.engine;
 
 import java.io.File;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
 
 import org.apache.camel.impl.DefaultCamelContext;
 import org.apache.camel.spi.PackageScanResourceResolver;
+import org.apache.camel.spi.Resource;
 import org.apache.camel.support.PluginHelper;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
 import static org.assertj.core.api.Assertions.assertThat;
 
@@ -48,4 +59,49 @@ public class DefaultPackageScanResourceResolverTest {
         assertThat(resolver.findResources("file:src/test/resources/org/apache/camel/impl/engine/c?/*.xml"))
                 .isEmpty();
     }
+
+    @Test
+    public void testJarResourcesScanWithSubdirectories(@TempDir Path tempDir) throws Exception {
+        Path jarPath = tempDir.resolve("test-routes.jar");
+        try (JarOutputStream jos = new JarOutputStream(Files.newOutputStream(jarPath))) {
+            addJarDirectory(jos, "routes/");
+            addJarDirectory(jos, "routes/sub1/");
+            addJarDirectory(jos, "routes/sub2/");
+            addJarEntry(jos, "routes/sub1/a.txt", "content-a");
+            addJarEntry(jos, "routes/sub2/b.txt", "content-b");
+            addJarEntry(jos, "routes/c.txt", "content-c");
+        }
+
+        URLClassLoader jarClassLoader = new URLClassLoader(new URL[] { jarPath.toUri().toURL() });
+        try (DefaultCamelContext ctx = new DefaultCamelContext()) {
+            PackageScanResourceResolver resolver = PluginHelper.getPackageScanResourceResolver(ctx);
+            resolver.addClassLoader(jarClassLoader);
+
+            Collection<Resource> resources = resolver.findResources("classpath:routes/**/*.txt");
+
+            assertThat(resources).hasSize(3);
+
+            for (Resource resource : resources) {
+                try (InputStream is = resource.getInputStream()) {
+                    assertThat(is).isNotNull();
+                    String content = new String(is.readAllBytes(), StandardCharsets.UTF_8);
+                    assertThat(content).startsWith("content-");
+                }
+            }
+        } finally {
+            jarClassLoader.close();
+        }
+    }
+
+    private static void addJarDirectory(JarOutputStream jos, String name) throws Exception {
+        JarEntry entry = new JarEntry(name);
+        jos.putNextEntry(entry);
+        jos.closeEntry();
+    }
+
+    private static void addJarEntry(JarOutputStream jos, String name, String content) throws Exception {
+        jos.putNextEntry(new JarEntry(name));
+        jos.write(content.getBytes(StandardCharsets.UTF_8));
+        jos.closeEntry();
+    }
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanJarResource.java b/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanJarResource.java
index ac063c4bd3a8..3c30444f65a2 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanJarResource.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/scan/PackageScanJarResource.java
@@ -22,20 +22,17 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
-import java.net.URLClassLoader;
+import java.net.URLConnection;
 
 import org.apache.camel.support.ResourceSupport;
-import org.apache.camel.util.StringHelper;
 
 public class PackageScanJarResource extends ResourceSupport {
 
     private final URL url;
-    private final URLClassLoader uc;
 
     public PackageScanJarResource(String scheme, URL url, String shortName) {
         super(scheme, url.getFile() + shortName);
         this.url = url;
-        this.uc = new URLClassLoader(new URL[] { url });
     }
 
     @Override
@@ -59,8 +56,14 @@ public class PackageScanJarResource extends ResourceSupport {
 
     @Override
     public InputStream getInputStream() throws IOException {
-        String loc = StringHelper.afterLast(getLocation(), "!");
-        return uc.getResourceAsStream(loc);
+        try {
+            URL jarUrl = URI.create("jar:" + getLocation()).toURL();
+            URLConnection con = jarUrl.openConnection();
+            con.setUseCaches(false);
+            return con.getInputStream();
+        } catch (Exception e) {
+            throw new IOException("Cannot open JAR resource: " + getLocation(), e);
+        }
     }
 
 }