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)

Reply via email to