[
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)