Serdar Gökay created CAMEL-26645:
------------------------------------
Summary: camel-core - Supervising route controller: route
consumers do not get the application class loader as TCCL, so camel-mail loses
attachments in camel run
Key: CAMEL-26645
URL: https://issues.apache.org/jira/browse/CAMEL-26645
Project: Camel
Issue Type: Bug
Components: camel-core
Affects Versions: 4.23.0
Reporter: Serdar Gökay
With the supervising route controller enabled, the threads of route consumers
do not have the application context class loader as their thread context class
loader (TCCL). With the controller off they normally do. Everything that looks
up services through the TCCL on those threads then sees only the parent class
path.
The visible case: under {{camel run}}, camel-mail silently loses attachments.
The IMAP consumer gets the message, the body is a raw stream, and the
attachment list is empty. Nothing is logged.
h3. Reproduce
Camel 4.23.0 CLI, Java 25, GreenMail 2.1.15 standalone
({{com.icegreen:greenmail-standalone}}) as the mail server.
{noformat}
java -Dgreenmail.setup.test.all -Dgreenmail.users=test:test@localhost
-Dgreenmail.hostname=127.0.0.1 -jar greenmail-standalone-2.1.15.jar
{noformat}
mail.camel.yaml:
{noformat}
- route:
from:
uri: imap://localhost:3143
parameters:
username: test
password: test
delay: 1000
delete: false
unseen: true
steps:
- log: "bodyType=${bodyType} attachments=${attachments.size}"
{noformat}
mail.eml:
{noformat}
From: a@localhost
To: test@localhost
Subject: with-attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="b1"
--b1
Content-Type: text/plain; charset=UTF-8
hello
--b1
Content-Type: application/octet-stream; name="orders.bin"
Content-Disposition: attachment; filename="orders.bin"
Content-Transfer-Encoding: base64
UEsDBAo=
--b1--
{noformat}
Send the mail and run the route, once per setting:
{noformat}
curl --url smtp://127.0.0.1:3025 --mail-from a@localhost --mail-rcpt
test@localhost --upload-file mail.eml
camel run mail.camel.yaml --prop=camel.routecontroller.enabled=false
bodyType=class jakarta.mail.internet.MimeMultipart attachments=1
curl --url smtp://127.0.0.1:3025 --mail-from a@localhost --mail-rcpt
test@localhost --upload-file mail.eml
camel run mail.camel.yaml --prop=camel.routecontroller.enabled=true
bodyType=class org.apache.camel.converter.stream.InputStreamCache
attachments=0
{noformat}
h3. What the threads see
Same mail route on 4.23.0, printing
{{Thread.currentThread().getContextClassLoader()}} on the consumer thread. A
timer route in the same runs shows the same class loader as the mail route, so
this is not specific to camel-mail.
||Route controller||Route started on||TCCL on the consumer thread||Attachments||
|off|main|DependencyDownloaderClassLoader|1|
|on, initialDelay=0|main|AppClassLoader|0|
|on, initialDelay=1000|controller thread|AppClassLoader|0|
|on, route in excludeRoutes|main|AppClassLoader|0|
h3. Cause
A consumer creates its threads when it is started, and they inherit the TCCL of
the thread starting the route. {{AbstractCamelContext}} sets the application
class loader as TCCL in {{doLifecycleChange()}}, which covers {{doStart()}}, so
routes started there are fine. {{AbstractCamelContext.start()}} calls
{{onCamelContextFullyStarted}} after that scope has closed, and two things
happen outside it:
* The supervising route controller starts its routes from that callback. The
second row shows the thread is not the point: the route starts on main and
still gets the wrong TCCL.
* camel-kafka creates its consumer threads from that callback. Their TCCL is
wrong even with the controller off.
A route started with {{getRouteController().startRoute(id)}} after the context
has started has the same gap, whatever the controller: it gets the TCCL of the
calling thread.
For camel-mail: the jakarta.activation API is on the application class path,
while the Angus provider and mail handlers are downloaded. With the wrong TCCL,
{{MailcapCommandMap}} finds no {{MailcapRegistryProvider}} and no handler for
multipart, so {{Message.getContent()}} returns the raw stream, and
{{MailBinding.extractAttachmentsFromMail}} sees no {{Multipart}}.
h3. Related
* CAMEL-22476 is the same cause in camel-plc4x, solved inside that component by
passing the application class loader to its driver manager.
* CAMEL-8339 set the TCCL in {{MailConfiguration}} and {{MailProducer}} for the
mail session, which is why sending works. It does not cover reading.
* {{RouteOnDemandReloadStrategy}} sets the application class loader as TCCL for
route reloads.
h3. Proposed change
Use the scope {{AbstractCamelContext}} already has, {{doLifecycleChange()}}, in
two more places: in {{InternalRouteStartupManager.safelyStartRouteServices}},
which every route start passes through, and around the
{{onCamelContextFullyStarted}} loop in {{AbstractCamelContext.start()}}. It
restores the previous TCCL afterwards and changes nothing when no application
class loader is set.
With this change on 4.23.0, all four rows above give
DependencyDownloaderClassLoader and attachments=1. On main, the KafkaConsumer
threads of camel-kafka get the application class loader in both modes, and new
unit tests fail without the change and pass with it. A PR follows.
_Claude Code on behalf of etzel-mes_
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)