[
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)