This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 7d9ef52e7848 CAMEL-26082: camel-sjms - preserveMessageQos also applies
to InOnly sends (#28590)
7d9ef52e7848 is described below
commit 7d9ef52e78489c4f89792c63d314dcd817eae920
Author: allthingssecurity <[email protected]>
AuthorDate: Wed Oct 28 21:34:47 2026 +0530
CAMEL-26082: camel-sjms - preserveMessageQos also applies to InOnly sends
(#28590)
With preserveMessageQos the endpoint priority, delivery mode and time to
live are used for InOnly sends and for exception replies of a consumer when the
message carries no QoS headers, even with explicitQosEnabled=false, as
camel-jms does. Documented in the 4.24 upgrade guide.
Co-Authored-By: Claude <[email protected]>
---
.../apache/camel/component/sjms/SjmsEndpoint.java | 9 +-
.../apache/camel/component/sjms/SjmsTemplate.java | 43 ++++-
.../sjms/consumer/EndpointMessageListener.java | 4 +-
.../producer/InOnlyPreserveMessageQosTest.java | 214 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 20 ++
5 files changed, 276 insertions(+), 14 deletions(-)
diff --git
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsEndpoint.java
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsEndpoint.java
index 55cd0de8f889..32c238cedb20 100644
---
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsEndpoint.java
+++
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsEndpoint.java
@@ -495,8 +495,8 @@ public class SjmsEndpoint extends DefaultEndpoint
public SjmsTemplate createInOnlyTemplate() {
SjmsTemplate template = new SjmsTemplate(getConnectionFactory(),
isTransacted(), getAcknowledgementMode().intValue());
- // configure qos if enabled
- if (isExplicitQosEnabled()) {
+ // configure qos if enabled, and with preserveMessageQos as the values
for a message without the headers
+ if (isExplicitQosEnabled() || isPreserveMessageQos()) {
int dm = isDeliveryPersistent() ? DeliveryMode.PERSISTENT :
DeliveryMode.NON_PERSISTENT;
if (getDeliveryMode() != null) {
dm = getDeliveryMode();
@@ -504,6 +504,7 @@ public class SjmsEndpoint extends DefaultEndpoint
template.setQoSSettings(dm, getPriority(), getTimeToLive());
}
template.setDestinationCreationStrategy(getDestinationCreationStrategy());
+ template.setPreserveMessageQos(isPreserveMessageQos());
return template;
}
@@ -515,8 +516,8 @@ public class SjmsEndpoint extends DefaultEndpoint
// must be auto-ack mode for InOut (request/reply mode)
SjmsTemplate template = new SjmsTemplate(getConnectionFactory(),
false, Session.AUTO_ACKNOWLEDGE);
- // configure qos if enabled
- if (isExplicitQosEnabled()) {
+ // configure qos if enabled, and with preserveMessageQos as the values
for a message without the headers
+ if (isExplicitQosEnabled() || isPreserveMessageQos()) {
int dm = isDeliveryPersistent() ? DeliveryMode.PERSISTENT :
DeliveryMode.NON_PERSISTENT;
if (getDeliveryMode() != null) {
dm = getDeliveryMode();
diff --git
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsTemplate.java
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsTemplate.java
index 12bdc59b8220..4d85e9838887 100644
---
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsTemplate.java
+++
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/SjmsTemplate.java
@@ -139,7 +139,7 @@ public class SjmsTemplate {
this.message = messageCreator.createMessage(session);
MessageProducer producer = session.createProducer(dest);
try {
- send(producer, message);
+ send(producer, message, exchange.getIn());
} finally {
closeProducer(producer);
}
@@ -170,6 +170,20 @@ public class SjmsTemplate {
}
public void send(MessageProducer producer, Message message) throws
Exception {
+ send(producer, message, null);
+ }
+
+ /**
+ * Sends the message with the producer.
+ *
+ * @param producer the producer
+ * @param message the JMS message
+ * @param camelMessage the Camel message the JMS message was created from.
With preserveMessageQos, the priority and
+ * delivery mode of the JMS message are only used when
this message has the JMSPriority or
+ * JMSDeliveryMode header, otherwise those of the
endpoint. When null, those of the JMS message
+ * are used.
+ */
+ public void send(MessageProducer producer, Message message,
org.apache.camel.Message camelMessage) throws Exception {
if (preserveMessageQos) {
long ttl = message.getJMSExpiration();
if (ttl != 0) {
@@ -178,19 +192,28 @@ public class SjmsTemplate {
if (ttl <= 0) {
ttl = 1;
}
+ } else {
+ // the message has no expiration, so use the time to live of
the endpoint (timeToLive, or the
+ // requestTimeout for request/reply), which is 0 (never
expires) when none is configured
+ ttl = this.timeToLive;
}
- int priority = message.getJMSPriority();
- if (priority < 0 || priority > 9) {
+ // a new JMS message always has a priority and delivery mode (the
binding sets PERSISTENT, and the
+ // priority is the default of the JMS client, 0 with ActiveMQ
Classic), so they are only taken from the
+ // message when the Camel message has the header; otherwise the
endpoint values are used
+ int priority = this.priority;
+ if (camelMessage == null ||
camelMessage.getHeader(JmsConstants.JMS_PRIORITY) != null) {
+ int messagePriority = message.getJMSPriority();
// use priority from endpoint if not provided on message with
a valid range
- priority = this.priority;
+ if (messagePriority >= 0 && messagePriority <= 9) {
+ priority = messagePriority;
+ }
}
- // if a delivery mode was set as a JMS header then we have used a
temporary
- // property to store it - CamelJMSDeliveryMode. Otherwise we could
not keep
- // track whether it was set or not as getJMSDeliveryMode() will
default return 1 regardless
- // if it was set or not, so we can never tell if end user provided
it in a header
- int resolvedDeliveryMode = resolveDeliveryMode(message);
+ int resolvedDeliveryMode = this.deliveryMode;
+ if (camelMessage == null ||
camelMessage.getHeader(JmsConstants.JMS_DELIVERY_MODE) != null) {
+ resolvedDeliveryMode = resolveDeliveryMode(message);
+ }
producer.send(message, resolvedDeliveryMode, priority, ttl);
} else if (explicitQosEnabled) {
@@ -202,6 +225,8 @@ public class SjmsTemplate {
private static int resolveDeliveryMode(Message message) throws
JMSException {
int resolvedDeliveryMode;
+ // the sjms binding sets the JMSDeliveryMode header with
setJMSDeliveryMode, so the binding never creates this
+ // property; it is kept for a JMS message that carries it as a property
if (JmsMessageHelper.hasProperty(message,
JmsConstants.JMS_DELIVERY_MODE)) {
resolvedDeliveryMode =
message.getIntProperty(JmsConstants.JMS_DELIVERY_MODE);
// remove the temporary property
diff --git
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/EndpointMessageListener.java
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/EndpointMessageListener.java
index 3e16b22f85fb..012e353972ff 100644
---
a/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/EndpointMessageListener.java
+++
b/components/camel-sjms/src/main/java/org/apache/camel/component/sjms/consumer/EndpointMessageListener.java
@@ -45,6 +45,7 @@ import org.apache.camel.component.sjms.SjmsMessage;
import org.apache.camel.component.sjms.SjmsTemplate;
import org.apache.camel.component.sjms.jms.JmsMessageHelper;
import org.apache.camel.support.AsyncProcessorConverterHelper;
+import org.apache.camel.support.DefaultMessage;
import org.apache.camel.util.ObjectHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -315,7 +316,8 @@ public class EndpointMessageListener implements
SessionMessageListener {
producer = messageProducerCreator.create(session,
endpoint, replyDestination);
- template.send(producer, reply);
+ // an exception reply has no Camel message: it has no QoS
headers, so it gets the QoS of the endpoint
+ template.send(producer, reply, out != null ? out : new
DefaultMessage(exchange));
} finally {
close(producer);
}
diff --git
a/components/camel-sjms/src/test/java/org/apache/camel/component/sjms/producer/InOnlyPreserveMessageQosTest.java
b/components/camel-sjms/src/test/java/org/apache/camel/component/sjms/producer/InOnlyPreserveMessageQosTest.java
new file mode 100644
index 000000000000..c5d336c4c833
--- /dev/null
+++
b/components/camel-sjms/src/test/java/org/apache/camel/component/sjms/producer/InOnlyPreserveMessageQosTest.java
@@ -0,0 +1,214 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.sjms.producer;
+
+import java.util.Map;
+
+import jakarta.jms.DeliveryMode;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.component.sjms.support.JmsTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * preserveMessageQos=true applies the JMSPriority, JMSDeliveryMode and
JMSExpiration headers also to InOnly sends, not
+ * only to InOut sends, and a message without one of these headers gets the
priority, delivery mode or time to live of
+ * the endpoint.
+ */
+class InOnlyPreserveMessageQosTest extends JmsTestSupport {
+
+ private static final String QUEUE =
"sjms:queue:in.only.preserve.qos.InOnlyPreserveMessageQosTest";
+ private static final String EXPIRY_QUEUE =
"sjms:queue:in.only.preserve.qos.expiry.InOnlyPreserveMessageQosTest";
+ private static final String REQUEST_QUEUE =
"sjms:queue:in.only.preserve.qos.request.InOnlyPreserveMessageQosTest";
+ private static final String REPLY_QUEUE =
"sjms:queue:in.only.preserve.qos.reply.InOnlyPreserveMessageQosTest";
+ private static final String FAILING_QUEUE =
"sjms:queue:in.only.preserve.qos.failing.InOnlyPreserveMessageQosTest";
+ private static final String EXCEPTION_REPLY_QUEUE =
"in.only.preserve.qos.exception.reply.InOnlyPreserveMessageQosTest";
+
+ @Test
+ void priorityAndDeliveryModeFromTheHeadersAreUsed() throws Exception {
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("Hello World");
+ result.message(0).header("JMSPriority").isEqualTo(8);
+
result.message(0).header("JMSDeliveryMode").isEqualTo(DeliveryMode.NON_PERSISTENT);
+
+ template.sendBodyAndHeaders(QUEUE + "?preserveMessageQos=true", "Hello
World",
+ Map.of("JMSPriority", 8, "JMSDeliveryMode",
DeliveryMode.NON_PERSISTENT));
+
+ result.assertIsSatisfied();
+ }
+
+ @Test
+ void priorityAndDeliveryModeOfTheEndpointAreUsedWithoutHeaders() throws
Exception {
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("Hello World");
+ // a new JMS message has a priority (the default of the JMS client)
and the delivery mode set by the binding
+ // (PERSISTENT), which must not win over the endpoint when the route
did not set the headers
+ result.message(0).header("JMSPriority").isEqualTo(7);
+
result.message(0).header("JMSDeliveryMode").isEqualTo(DeliveryMode.NON_PERSISTENT);
+
+ template.sendBody(QUEUE +
"?preserveMessageQos=true&priority=7&deliveryPersistent=false", "Hello World");
+
+ result.assertIsSatisfied();
+ }
+
+ @Test
+ void headersWinOverTheEndpoint() throws Exception {
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("Hello World");
+ result.message(0).header("JMSPriority").isEqualTo(2);
+
result.message(0).header("JMSDeliveryMode").isEqualTo(DeliveryMode.PERSISTENT);
+
+ template.sendBodyAndHeaders(QUEUE +
"?preserveMessageQos=true&priority=7&deliveryPersistent=false", "Hello World",
+ Map.of("JMSPriority", 2, "JMSDeliveryMode", "PERSISTENT"));
+
+ result.assertIsSatisfied();
+ }
+
+ @Test
+ void priorityAndDeliveryModeOfTheEndpointAreUsedWithExplicitQosDisabled()
throws Exception {
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("Hello World");
+ result.message(0).header("JMSPriority").isEqualTo(7);
+
result.message(0).header("JMSDeliveryMode").isEqualTo(DeliveryMode.NON_PERSISTENT);
+
+ template.sendBody(QUEUE +
"?preserveMessageQos=true&explicitQosEnabled=false&priority=7&deliveryPersistent=false",
+ "Hello World");
+
+ result.assertIsSatisfied();
+ }
+
+ @Test
+ void
exceptionReplyOfAConsumerUsesThePriorityAndDeliveryModeOfTheEndpoint() throws
Exception {
+ MockEndpoint reply = getMockEndpoint("mock:exceptionReply");
+ reply.expectedMessageCount(1);
+ reply.message(0).header("JMSPriority").isEqualTo(7);
+
reply.message(0).header("JMSDeliveryMode").isEqualTo(DeliveryMode.NON_PERSISTENT);
+ reply.message(0).body().isInstanceOf(IllegalStateException.class);
+
+ // InOnly with a JMSReplyTo header: the failing consumer sends the
exception reply to the reply queue
+ template.sendBodyAndHeader(FAILING_QUEUE + "?preserveMessageQos=true",
"Hello World", "JMSReplyTo",
+ EXCEPTION_REPLY_QUEUE);
+
+ reply.assertIsSatisfied();
+ }
+
+ @Test
+ void requestUsesThePriorityAndDeliveryModeOfTheEndpointWithoutHeaders()
throws Exception {
+ MockEndpoint request = getMockEndpoint("mock:request");
+ request.expectedBodiesReceived("Hello World");
+ request.message(0).header("JMSPriority").isEqualTo(7);
+
request.message(0).header("JMSDeliveryMode").isEqualTo(DeliveryMode.NON_PERSISTENT);
+
+ Object reply = template.requestBody(REQUEST_QUEUE +
"?preserveMessageQos=true&priority=7&deliveryPersistent=false",
+ "Hello World");
+
+ request.assertIsSatisfied();
+ assertEquals("Bye World", reply);
+ }
+
+ @Test
+ void replyOfAConsumerUsesThePriorityHeader() {
+ Exchange out = template.request(REPLY_QUEUE, e ->
e.getIn().setBody("Hello World"));
+
+ assertEquals("Bye World", out.getMessage().getBody(String.class));
+ // the consumer endpoint has preserveMessageQos=true and its route
sets JMSPriority on the reply
+ assertEquals(9, out.getMessage().getHeader("JMSPriority",
Integer.class));
+ }
+
+ @Test
+ void expirationFromTheHeaderIsUsed() throws Exception {
+ long expiration = System.currentTimeMillis() + 600_000;
+
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("Hello World");
+ // without the header the message would never expire (JMSExpiration 0)
+ result.message(0).header("JMSExpiration").isGreaterThan(expiration -
60_000);
+
+ template.sendBodyAndHeader(QUEUE + "?preserveMessageQos=true", "Hello
World", "JMSExpiration", expiration);
+
+ result.assertIsSatisfied();
+ }
+
+ @Test
+ void timeToLiveOfTheEndpointIsUsedForAMessageWithoutExpiration() throws
Exception {
+ long before = System.currentTimeMillis();
+
+ MockEndpoint result = getMockEndpoint("mock:result");
+ result.expectedBodiesReceived("Hello World");
+
result.message(0).header("JMSExpiration").isGreaterThanOrEqualTo(before +
600_000);
+
+ template.sendBody(QUEUE +
"?preserveMessageQos=true&timeToLive=600000", "Hello World");
+
+ result.assertIsSatisfied();
+ }
+
+ @Test
+ void requestTimeoutIsUsedForARequestWithoutExpiration() throws Exception {
+ long before = System.currentTimeMillis();
+
+ MockEndpoint request = getMockEndpoint("mock:request");
+ request.expectedBodiesReceived("Hello World");
+
request.message(0).header("JMSExpiration").isGreaterThanOrEqualTo(before +
20_000);
+
+ Object reply = template.requestBody(REQUEST_QUEUE +
"?preserveMessageQos=true&requestTimeout=20000", "Hello World");
+
+ request.assertIsSatisfied();
+ assertEquals("Bye World", reply);
+ }
+
+ @Test
+ void anExpiredMessageIsNotDelivered() throws Exception {
+ MockEndpoint expiry = getMockEndpoint("mock:expiry");
+ expiry.expectedBodiesReceived("Fresh");
+ expiry.setAssertPeriod(500);
+
+ // expired one minute ago: sent with the shortest time to live, so the
broker drops it
+ template.sendBodyAndHeader(EXPIRY_QUEUE + "?preserveMessageQos=true",
"Stale", "JMSExpiration",
+ System.currentTimeMillis() - 60_000);
+ template.sendBody(EXPIRY_QUEUE, "Fresh");
+ // the consumer starts only after both messages were sent
+ context.getRouteController().startRoute("expiry");
+
+ expiry.assertIsSatisfied();
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ public void configure() {
+ from(QUEUE).to("mock:result");
+
+
from(REQUEST_QUEUE).to("mock:request").transform().constant("Bye World");
+
+ from(REPLY_QUEUE +
"?preserveMessageQos=true").setHeader("JMSPriority", constant(9))
+ .transform().constant("Bye World");
+
+
from(EXPIRY_QUEUE).routeId("expiry").autoStartup(false).to("mock:expiry");
+
+ // an exception reply is an ObjectMessage, which needs
objectMessageEnabled on both sides
+ from(FAILING_QUEUE +
"?preserveMessageQos=true&transferException=true&objectMessageEnabled=true"
+ + "&priority=7&deliveryPersistent=false")
+ .throwException(new IllegalStateException("Forced"));
+ from("sjms:queue:" + EXCEPTION_REPLY_QUEUE +
"?objectMessageEnabled=true").to("mock:exceptionReply");
+ }
+ };
+ }
+}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
index 7767b53870a0..d29de39c38f8 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc
@@ -1945,6 +1945,26 @@ live, so it never expired, even when the endpoint has a
`timeToLive` and, for re
`requestTimeout` as time to live otherwise. Such a message now gets the time
to live of the endpoint, as the option
documents. Set `disableTimeToLive=true` to send it without time to live.
+=== camel-sjms - preserveMessageQos also applies to InOnly sends
+
+With `preserveMessageQos=true`, an InOnly send ignored the `JMSPriority`,
`JMSDeliveryMode` and `JMSExpiration`
+headers, and used the QoS of the endpoint (or of the JMS client); only
request/reply used them. An InOnly send now
+uses these headers, as camel-jms does. A message whose `JMSExpiration` is in
the past expires at once, and a message
+consumed from JMS and sent on keeps the priority, delivery mode and expiration
of the consumed message. The replies of
+a consumer endpoint with `preserveMessageQos=true` use the headers of the
reply message too; these are still the
+headers of the request unless the route changes or removes them. An exception
reply (`transferException=true`) has
+no headers and uses the QoS of the endpoint. `camel-sjms2` behaves the same.
+
+A header that is not set falls back to the endpoint, as the option documents,
also with `explicitQosEnabled=false`: to
+the `priority` (a `priority` of 0 is not applied, as with
`explicitQosEnabled`) and the `deliveryPersistent` (or
+`deliveryMode`) of the endpoint, and, for a message without `JMSExpiration`
(header missing or
+0), to the time to live of the endpoint (`timeToLive`, or the `requestTimeout`
for request/reply). This also changes
+request/reply: before, a request without `JMSPriority` was sent with the
priority of the new JMS message (the default
+of the JMS client, 0 with ActiveMQ Classic), a request without
`JMSDeliveryMode` was persistent whatever the endpoint
+said, and a request without `JMSExpiration` never expired. To send a request
without time to live, set
+`disableTimeToLive=true` and no `timeToLive`. Remove `preserveMessageQos=true`
to keep using only the QoS of the
+endpoint for InOnly sends.
+
=== camel-pulsar - the producer continues the sequence ids of the broker
The `pulsar` producer no longer passes the default `initialSequenceId=-1` to
the Pulsar client, so the client