CAMEL-23835: camel-launcher - set classloader on embedded plugins so TUI ServiceLoader works in fat-jar#24258
Conversation
|
🌟 Thank you for your contribution to the Apache Camel project! 🌟 🐫 Apache Camel Committers, please review the following items:
|
|
🧪 CI tested the following changed modules:
💡 Manual integration tests recommended:
All tested modules (2 modules)
|
…TUI ServiceLoader works in fat-jar The launcher registers its bundled plugins directly in CamelLauncherMain.postAddCommands by instantiating them and calling customize, without ever setting a classloader on them. The TUI plugin relies on that classloader: CamelMonitor installs it as the thread-context classloader so tamboui can discover its terminal backend via ServiceLoader. With a null classloader the TCCL becomes null and ServiceLoader falls back to the system classloader, which cannot see the nested BOOT-INF/lib jars of the Spring Boot fat-jar. As a result `camel tui` from the camel-launcher failed with "No BackendProvider found on classpath". Fix: postAddCommands now sets the fat-jar classloader (this class's classloader) on each embedded plugin before customizing it. Verified end-to-end against the rebuilt fat-jar: the TUI now starts instead of failing. Added a regression test that fails without the fix. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
davsclaus
left a comment
There was a problem hiding this comment.
LGTM — clean fix for the TUI "No BackendProvider found" failure in the fat-jar. Setting the classloader on all embedded plugins in postAddCommands is the right approach, and the regression test verifies the wiring end-to-end.
This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.
Summary
Running the TUI from the
camel-launcherfat-jar (java -jar camel-launcher-*.jar tui,./camel.sh tui, orpodman run camel-launcher tui) fails immediately with:The same command works under JBang. This is not a packaging problem:
tamboui-jline3-backendand itsMETA-INF/services/dev.tamboui.terminal.BackendProviderSPI entry are correctly bundled underBOOT-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 plugin classloader as the TCCL before starting (CamelMonitor.java:266-267):The launcher registers its bundled plugins in
CamelLauncherMain.postAddCommandsby instantiating them directly and callingcustomize(...), but never callingsetClassLoader:So
TuiPlugin.classLoader(and theCamelMonitorit creates) isnull, the TCCL is set tonull, andServiceLoaderfalls back to the system class loader (AppClassLoader). In a Spring Boot fat-jar that loader cannot see the nestedBOOT-INF/lib/*.jar(onlyLaunchedClassLoadercan), so the backend is never found. tamboui'sSafeServiceLoader.load(Class)then swallows the resultingServiceConfigurationError/LinkageError(it passes anullerror handler), producing the misleading "No BackendProvider found".Evidence gathered inside the actual fat-jar:
The embedded-plugin scanning path (
PluginHelper.addEmbeddedPlugins) is not involved here: it finds the service resources but their nested URLs (jar:nested:.../fat.jar/!BOOT-INF/lib/...) makenew JarFile(...)throw, so it silently registers nothing. The launcher relies entirely onpostAddCommands. JBang works because there the TUI plugin is a downloaded plugin loaded through a path that already callssetClassLoader(...).Fix
CamelLauncherMain.postAddCommandsnow sets the fat-jar classloader (this class's classloader, i.e.LaunchedClassLoader) on each embedded plugin before customizing it:Plugin.setClassLoaderis adefaultno-op, so this is harmless for the other bundled plugins.Verification
java -jar camel-launcher-*.jar tuino longer throwsBackendException(zero occurrences of "No BackendProvider found"); JLine's terminal provider initializes and the TUI enters raw mode and renders.CamelLauncherTest#testEmbeddedTuiPluginReceivesClassLoaderdrivespostAddCommandsand asserts the registered TUImonitorcommand received a non-null classloader that can loaddev.tamboui.terminal.BackendProvider. Verified it fails without the fix (expected: not <null>) and passes with it. FullCamelLauncherTestgreen.Notes
SafeServiceLoader.load(Class)discards the realLinkageError/ServiceConfigurationError). Worth reporting to tamboui separately; out of scope here.Claude Code on behalf of Adriano Machado
🤖 Generated with Claude Code