[ 
https://issues.apache.org/jira/browse/CAMEL-23835?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Claus Ibsen resolved CAMEL-23835.
---------------------------------
    Fix Version/s: 4.21.0
       Resolution: Fixed

> camel-launcher: 'camel tui' fails with "No BackendProvider found" because 
> embedded plugins are loaded without a classloader
> ---------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-23835
>                 URL: https://issues.apache.org/jira/browse/CAMEL-23835
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-jbang
>    Affects Versions: 4.20.0
>            Reporter: Adriano Machado
>            Assignee: Adriano Machado
>            Priority: Major
>             Fix For: 4.21.0
>
>
> h2. Summary
> Running the TUI from the {{camel-launcher}} fat-jar (e.g. {{./camel.sh tui}} 
> or {{podman run camel-launcher tui}}) fails immediately with:
> {code}
> 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)
>     at 
> org.apache.camel.dsl.jbang.core.commands.tui.CamelMonitor.doCall(CamelMonitor.java:323)
>     ...
> {code}
> The same command works when the CLI is installed via JBang. This is *not* a 
> packaging problem: the backend jar ({{tamboui-jline3-backend}}) and its 
> {{META-INF/services/dev.tamboui.terminal.BackendProvider}} SPI
> entry are correctly bundled in the fat-jar under {{BOOT-INF/lib/}}.
> h2. Affects
> * Module: {{dsl/camel-jbang/camel-launcher}} (and any embedded/fat-jar plugin 
> scenario)
> * Version: 4.21.0-SNAPSHOT
> h2. Steps to reproduce
> # Build the launcher: {{mvn -o install -Dquickly -pl 
> dsl/camel-jbang/camel-launcher -am}}
> # Unpack {{camel-launcher-*-bin.zip}} and run {{./camel.sh tui}} (or {{podman 
> run -it --rm camel-launcher tui}}).
> # Observe the {{No BackendProvider found}} failure.
> h2. Root cause
> TamboUI discovers its terminal backend via 
> {{ServiceLoader.load(BackendProvider.class)}}, which keys off the 
> *thread-context classloader* (TCCL). To make this work, the TUI deliberately 
> installs its own
> classloader as the TCCL before starting:
> {code:java}
> // CamelMonitor.java:266-267
> // to make ServiceLoader work with tamboui for downloaded JARs
> Thread.currentThread().setContextClassLoader(classLoader);
> {code}
> That {{classLoader}} arrives as {{null}} for *embedded* (fat-jar) plugins, 
> because the embedded-plugin loader never sets it. 
> {{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(...)}}), it omits the {{setClassLoader}} call. As a 
> result
> {{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*. In a Spring Boot fat-jar the system loader is {{AppClassLoader}}, 
> which cannot see the nested {{BOOT-INF/lib/*.jar}} (only
> {{LaunchedClassLoader}} can). The provider is never found. TamboUI's 
> {{SafeServiceLoader.load(Class)}} then swallows the resulting 
> {{ServiceConfigurationError}}/{{LinkageError}} (it passes a {{null}} error
> handler) and reports the misleading "No BackendProvider found".
> Proven directly inside the actual fat-jar:
> {code}
> probe loaded by: LaunchedClassLoader
> system CL:       AppClassLoader
> [TCCL=null]     SPI class not visible: ClassNotFoundException: 
> dev.tamboui.terminal.BackendProvider  -> providers=-1
> [TCCL=launched] providers=1
> {code}
> h2. Why JBang works
> Under JBang the TUI plugin is a *downloaded* plugin, so it goes through a 
> code path that calls {{setClassLoader(...)}} with a non-null classloader that 
> chains to the TamboUI jars. The TCCL is therefore valid and
> discovery succeeds.
> h2. Fix
> In {{PluginHelper.loadPluginFromService(...)}}, hand the loading classloader 
> to the plugin before customizing it, mirroring the downloaded-plugin paths:
> {code:java}
> Plugin plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
> plugin.setClassLoader(classLoader);   // <-- added
> {code}
> {{Plugin.setClassLoader}} is a {{default}} no-op, so this is harmless for 
> every other bundled plugin.
> h2. Tests
> Added {{PluginHelperTest#testEmbeddedPluginReceivesClassLoader}}, which 
> drives {{loadPluginFromService}} with a capturing {{Plugin}} and asserts it 
> receives the exact classloader used to load it. Verified the test
> fails without the fix ({{expected: <1> but was: <0>}}) and passes with it.
> h2. Notes
> * The misleading error message originates upstream in TamboUI 
> ({{SafeServiceLoader.load(Class)}} discards the real 
> {{LinkageError}}/{{ServiceConfigurationError}}). Worth reporting to TamboUI 
> separately; not in
> scope for this fix.
> * No upgrade-guide entry is needed: this is a pure bug fix with nothing to 
> migrate.
> _Reported by Claude Code on behalf of Adriano Machado._



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to