ammachado opened a new pull request, #24258:
URL: https://github.com/apache/camel/pull/24258

   ## Summary
   
   Running the TUI from the `camel-launcher` fat-jar (`./camel.sh tui` or 
`podman run camel-launcher tui`) fails immediately with:
   
   ```
   dev.tamboui.terminal.BackendException: No BackendProvider found on classpath.
   Add a backend dependency such as tamboui-jline3-backend or 
tamboui-panama-backend.
        at 
dev.tamboui.terminal.BackendFactory.tryProviders(BackendFactory.java:139)
        at dev.tamboui.terminal.BackendFactory.create(BackendFactory.java:91)
        at dev.tamboui.tui.TuiRunner.create(TuiRunner.java:186)
        at 
org.apache.camel.dsl.jbang.core.commands.tui.TuiBackendHelper.createTuiRunner(TuiBackendHelper.java:36)
        ...
   ```
   
   The same command works when the CLI is installed via JBang. This is **not** 
a packaging problem: `tamboui-jline3-backend` and its 
`META-INF/services/dev.tamboui.terminal.BackendProvider` SPI entry are 
correctly bundled under `BOOT-INF/lib/`.
   
   JIRA: https://issues.apache.org/jira/browse/CAMEL-23835
   
   ## Root cause
   
   TamboUI discovers its terminal backend via 
`ServiceLoader.load(BackendProvider.class)`, which keys off the 
**thread-context classloader** (TCCL). The TUI deliberately installs its own 
classloader as the TCCL before starting (`CamelMonitor.java:266-267`):
   
   ```java
   // to make ServiceLoader work with tamboui for downloaded JARs
   Thread.currentThread().setContextClassLoader(classLoader);
   ```
   
   That `classLoader` arrives as `null` for **embedded** (fat-jar) plugins, 
because `PluginHelper.loadPluginFromService(...)` instantiates the plugin and 
calls `customize(...)` but, unlike the two downloaded-plugin paths 
(`PluginHelper.java:344` and `:520`, which both call 
`instance.setClassLoader(...)`), never calls `setClassLoader`. So 
`TuiPlugin.classLoader` stays `null` → `CamelMonitor.classLoader` is `null` → 
the TCCL is set to `null`.
   
   With a `null` TCCL, `ServiceLoader` falls back to the system class loader 
(`AppClassLoader`), which cannot see the nested `BOOT-INF/lib/*.jar` of a 
Spring Boot fat-jar (only `LaunchedClassLoader` can). The provider is never 
found, and TamboUI's `SafeServiceLoader.load(Class)` silently swallows the 
resulting `ServiceConfigurationError`/`LinkageError` (it passes a `null` error 
handler), producing the misleading "No BackendProvider found".
   
   Proven directly inside the actual fat-jar:
   
   ```
   probe loaded by: LaunchedClassLoader
   system CL:       AppClassLoader
   [TCCL=null]     SPI class not visible: ClassNotFoundException: 
dev.tamboui.terminal.BackendProvider  -> providers=-1
   [TCCL=launched] providers=1
   ```
   
   JBang works because there the TUI plugin is a *downloaded* plugin, which 
goes through a path that calls `setClassLoader(...)` with a non-null 
classloader chaining to the TamboUI jars.
   
   ## Fix
   
   In `PluginHelper.loadPluginFromService(...)`, hand the loading classloader 
to the plugin before customizing it, mirroring the downloaded-plugin paths:
   
   ```java
   Plugin plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
   plugin.setClassLoader(classLoader);   // added
   ```
   
   `Plugin.setClassLoader` is a `default` no-op, so this is harmless for every 
other bundled plugin.
   
   ## Tests
   
   Added `PluginHelperTest#testEmbeddedPluginReceivesClassLoader`, which drives 
`loadPluginFromService` with a capturing `Plugin` and asserts it receives the 
exact classloader used to load it. Verified it fails without the fix 
(`expected: <1> but was: <0>`) and passes with it. Full `PluginHelperTest` (10 
tests) green.
   
   ## Notes
   
   - The misleading error message originates upstream in TamboUI 
(`SafeServiceLoader.load(Class)` discards the real 
`LinkageError`/`ServiceConfigurationError`). Worth reporting to TamboUI 
separately; out of scope here.
   - No upgrade-guide entry: pure bug fix with nothing to migrate.
   
   _Claude Code on behalf of Adriano Machado_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to