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

Claus Ibsen resolved CAMEL-26692.
---------------------------------
    Resolution: Fixed

Merged: https://github.com/apache/camel/pull/29340 (main)

_Claude Code on behalf of davsclaus_

> camel-jms, camel-amqp, camel-activemq - a send from a transacted route is not 
> rolled back when username/password are set
> ------------------------------------------------------------------------------------------------------------------------
>
>                 Key: CAMEL-26692
>                 URL: https://issues.apache.org/jira/browse/CAMEL-26692
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-activemq, camel-amqp, camel-jms
>    Affects Versions: 4.18.5, 4.22.2, 4.23.0
>            Reporter: Federico Mariani
>            Assignee: Federico Mariani
>            Priority: Major
>             Fix For: 4.24.0
>
>
> h3. Problem
> In a transacted route such as 
> {{from("jms:queue:in?transacted=true").to("jms:queue:out")}}, the message 
> sent to {{out}} takes part in the transaction of the consumed message: when 
> the route fails, both are rolled back and the broker delivers the consumed 
> message again. This no longer holds when {{username}} and {{password}} are 
> set, on the component or on the endpoints. The message sent to {{out}} is 
> committed at once, the consumed message is rolled back and delivered again, 
> and the route sends it a second time: {{out}} gets duplicates.
> Source on main (48d063cfde0a), 
> {{components/camel-jms/src/main/java/org/apache/camel/component/jms/}}:
> * {{JmsComponent.java}} lines 1244-1259 ({{createEndpoint}}): when a username 
> and a password are set, it wraps the connection factory of the endpoint in a 
> new {{UserCredentialsConnectionFactoryAdapter}} (line 1253), for each 
> endpoint. Lines 1245-1246 take the username and password of the component 
> when the endpoint has none, so with credentials on the component every 
> endpoint gets its own adapter instance.
> * {{JmsConfiguration.java}}: the consumer endpoint gives its own connection 
> factory to the listener container (line 1697, through 
> {{getOrCreateListenerConnectionFactory}}, line 1869) and to the lazily 
> created {{JmsTransactionManager}} (line 1884). The producer endpoint gives 
> its own to the {{JmsTemplate}} (line 780, through 
> {{createTemplateConnectionFactory}}, line 1876).
> * Spring looks up the session of the transaction by the connection factory 
> instance: {{JmsTransactionManager}} binds it under its factory (spring-jms 
> 7.0.10, {{JmsTransactionManager.java}} line 234), and {{JmsTemplate.execute}} 
> (line 525) looks it up with its own factory ({{ConnectionFactoryUtils.java}} 
> line 286). Two adapter instances never match, so the producer opens its own 
> session and commits the send at once.
> * Without credentials, the endpoints share the connection factory instance of 
> the component, and the send is part of the transaction.
> h3. How to reproduce
> Measured on main 48d063cfde0a with a test in each module (not committed):
> * camel-jms: embedded Artemis ({{ArtemisServiceFactory.createVMService()}}), 
> {{JmsComponent}} with an Artemis {{ActiveMQConnectionFactory}} as 
> {{connectionFactory}}.
> * camel-amqp: embedded Artemis with the AMQP acceptor 
> ({{ArtemisServiceFactory.createAMQPService()}}), {{AMQPComponent}} with 
> {{brokerUrl}}.
> * Route: 
> {{from("<scheme>:queue:in?transacted=true").to("<scheme>:queue:out")}}, then 
> a processor that throws on the first delivery only. A second route consumes 
> {{out}} into a mock. One message is sent to {{in}}; the test waits for the 
> redelivery and a quiet period of 2 seconds, then counts the messages on 
> {{out}}.
> * The test brokers do not check credentials, so the username and password 
> change only how the endpoints are wired.
> ||Component||Credentials||Deliveries of the consumed message||Messages on 
> out||
> |camel-jms|none (control)|2|1|
> |camel-jms|{{username}}/{{password}} on the component|2|2|
> |camel-jms|{{username=camel&password=rider}} on both endpoints|2|2|
> |camel-amqp|none (control)|2|1|
> |camel-amqp|{{username}}/{{password}} on the component|2|2|
> |camel-amqp|{{username=camel&password=rider}} on both endpoints|2|2|
> *camel-activemq* was not run against a broker (its tests use a container). 
> {{ActiveMQComponent}} does not override {{createEndpoint}}, so it takes the 
> same path. A check without a broker, which only creates the endpoints and 
> compares the instances, gives:
> * {{connectionFactory}} set on the component, no credentials: the transaction 
> manager of the consumer endpoint and the template of the producer endpoint 
> use the same instance.
> * The same with {{username}}/{{password}} on the component: two different 
> {{UserCredentialsConnectionFactoryAdapter}} instances.
> * Also, with only {{brokerURL}} set ({{usePooledConnection=false}} for the 
> check), each endpoint creates its own {{ActiveMQConnectionFactory}}: 
> {{ActiveMQConfiguration#createConnectionFactory}} (line 170) runs lazily on 
> the configuration copy of each endpoint. So the instances differ even without 
> credentials. With the default pooled factory, that would be one pool per 
> endpoint; this comes from reading the source only, as pooled-jms is not on 
> the test classpath of the module.
> h3. Proposed change
> * In {{JmsComponent#createEndpoint}}, reuse one adapter for the endpoints 
> that wrap the same connection factory with the same username and password 
> (for example created on the component), instead of a new one per endpoint. 
> Endpoints with other credentials or their own {{connectionFactory}} keep 
> their own factory: they cannot share a transaction anyway.
> * Keep credentials re-resolved when the routes are rebuilt: CAMEL-24637 (in 
> progress) relies on the fresh adapter of each endpoint to pick up rotated 
> credentials, so the shared adapter must be rebuilt with the component.
> * camel-activemq with {{brokerURL}} only: create the connection factory once 
> for the component rather than per endpoint configuration. This may be better 
> tracked as its own issue.
> * Add a test per component that sends from a transacted consumer route with 
> credentials and checks the rollback.
> * Workaround today: set the credentials on the connection factory itself (the 
> user and password of the client factory), not with the {{username}} and 
> {{password}} options.
> h3. Impact / upgrade
> * It changes behaviour for transacted routes with {{username}}/{{password}}: 
> the message sent from the route becomes part of the transaction, as without 
> credentials. This needs an entry in the 4.24 upgrade guide.
> * A producer in the thread of a consumer would then also use the session of 
> the consumer, so it no longer opens a connection of its own per message.
> * Not affected: credentials set on the connection factory, and routes without 
> a transaction.
> * The same code is in 4.18.5, 4.22.2 and 4.23.0 (read from the source; 
> spring-jms 6.2.19 on camel-4.18.x does the same lookup by factory instance, 
> {{ConnectionFactoryUtils.java}} line 297).
> h3. Related
> * CAMEL-26658: found while adding a shared producer connection to camel-amqp; 
> that change keeps the factory of each endpoint for the transaction lookup, so 
> it does not change this behaviour.
> * CAMEL-24637: re-authentication on secret rotation in camel-jms, which 
> relies on the adapter of each endpoint.
> * CAMEL-246 (commit b4e7378ab6cf, 2008): wraps the connection factory with 
> the username and password in {{createEndpoint}}. CAMEL-4329 (commit 
> 153aca005cc1, 2011): the same with a {{connectionFactory}} in the endpoint 
> URI. Commit 6b15b6536cae (2016) made the username and password of the 
> component the default of the endpoints.
> * CAMEL-20521: transacted sends with camel-amqp.
> _Claude Code on behalf of Federico Mariani_



--
This message was sent by Atlassian Jira
(v10.3.26#10030026)

Reply via email to