Federico Mariani created CAMEL-26692:
----------------------------------------

             Summary: 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.23.0, 4.22.2
            Reporter: Federico Mariani


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