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 832bd352d995 CAMEL-26712: camel-management - an exchange recovered by
a redelivery is not counted when the route removes the CamelRedelivered header
(#29380)
832bd352d995 is described below
commit 832bd352d9958f57d8fc67ec2087992c4a3a9692
Author: Claus Ibsen <[email protected]>
AuthorDate: Sat Nov 7 15:49:53 2026 +0100
CAMEL-26712: camel-management - an exchange recovered by a redelivery is
not counted when the route removes the CamelRedelivered header (#29380)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../errorhandler/RedeliveryErrorHandler.java | 3 +
.../ManagedRedeliverRemoveHeadersTest.java | 111 +++++++++++++++++++++
.../org/apache/camel/support/ExchangeHelper.java | 8 +-
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 8 ++
4 files changed, 129 insertions(+), 1 deletion(-)
diff --git
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
index 10fbcc955210..e14b33fec0b2 100644
---
a/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
+++
b/core/camel-core-processor/src/main/java/org/apache/camel/processor/errorhandler/RedeliveryErrorHandler.java
@@ -1676,6 +1676,9 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
exchange.getIn().removeHeader(Exchange.REDELIVERED);
exchange.getIn().removeHeader(Exchange.REDELIVERY_COUNTER);
exchange.getIn().removeHeader(Exchange.REDELIVERY_MAX_COUNTER);
+ // also reset the exchange counter; a continued exchange is not
redelivered
+ exchange.getExchangeExtension().setRedeliveryCounter(-1);
+ exchange.getExchangeExtension().setRedeliveryMaxCounter(-1);
exchange.getExchangeExtension().setFailureHandled(false);
// keep the Exchange.EXCEPTION_CAUGHT as property so end user
knows the caused exception
// and mark it as handled (continued), so it is not regarded as a
previous exception of a later failure
diff --git
a/core/camel-management/src/test/java/org/apache/camel/management/ManagedRedeliverRemoveHeadersTest.java
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRedeliverRemoveHeadersTest.java
new file mode 100644
index 000000000000..f6ca1944a002
--- /dev/null
+++
b/core/camel-management/src/test/java/org/apache/camel/management/ManagedRedeliverRemoveHeadersTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.management;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.DisabledOnOs;
+import org.junit.jupiter.api.condition.OS;
+
+import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_PROCESSOR;
+import static
org.apache.camel.management.DefaultManagementObjectNameStrategy.TYPE_ROUTE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * An exchange that completes after a redelivery counts as redelivered also
when the route removes the Camel headers
+ * after the redelivery, which takes the CamelRedelivered header away
(CAMEL-26712).
+ */
+@DisabledOnOs(OS.AIX)
+public class ManagedRedeliverRemoveHeadersTest extends ManagementTestSupport {
+
+ private final AtomicInteger calls = new AtomicInteger();
+
+ @Test
+ public void testRedeliveredAlsoWhenTheHeadersAreRemoved() throws Exception
{
+ MBeanServer mbeanServer = getMBeanServer();
+
+ getMockEndpoint("mock:result").expectedMessageCount(2);
+
getMockEndpoint("mock:result").message(0).header("CamelRedelivered").isNull();
+
+ template.sendBody("direct:removing", "Hello");
+ template.sendBody("direct:keeping", "World");
+
+ assertMockEndpointsSatisfied();
+
+ for (String route : new String[] { "removing", "keeping" }) {
+ ObjectName on = getCamelObjectName(TYPE_ROUTE, route);
+ assertEquals(1L, mbeanServer.getAttribute(on,
"ExchangesCompleted"), route);
+ assertEquals(0L, mbeanServer.getAttribute(on, "FailuresHandled"),
route);
+ assertEquals(1L, mbeanServer.getAttribute(on, "Redeliveries"),
route);
+
+ on = getCamelObjectName(TYPE_PROCESSOR, route + "-flaky");
+ assertEquals(1L, mbeanServer.getAttribute(on, "Redeliveries"),
route);
+ }
+ }
+
+ @Test
+ public void testContinuedIsNotRedelivered() throws Exception {
+ MBeanServer mbeanServer = getMBeanServer();
+
+ getMockEndpoint("mock:continued").expectedMessageCount(1);
+
+ template.sendBody("direct:continued", "Bye");
+
+ assertMockEndpointsSatisfied();
+
+ ObjectName on = getCamelObjectName(TYPE_ROUTE, "continued");
+ assertEquals(1L, mbeanServer.getAttribute(on, "ExchangesCompleted"));
+ assertEquals(0L, mbeanServer.getAttribute(on, "Redeliveries"));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
onException(IllegalStateException.class).maximumRedeliveries(2).redeliveryDelay(0);
+
onException(IllegalArgumentException.class).maximumRedeliveries(2).redeliveryDelay(0).continued(true);
+
+ // the first call fails, the redelivery succeeds; then the
Camel headers are removed
+ from("direct:removing").routeId("removing")
+ .process(e -> flaky()).id("removing-flaky")
+ .removeHeaders("Camel*")
+ .to("mock:result");
+
+ from("direct:keeping").routeId("keeping")
+ .process(e -> flaky()).id("keeping-flaky")
+ .to("mock:result");
+
+ // every attempt fails, and the failure is continued
+ from("direct:continued").routeId("continued")
+ .throwException(new IllegalArgumentException("Forced"))
+ .to("mock:continued");
+ }
+ };
+ }
+
+ private void flaky() {
+ if (calls.incrementAndGet() % 2 == 1) {
+ throw new IllegalStateException("stock down");
+ }
+ }
+}
diff --git
a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
index e8db04bf1570..49e1623ac3b1 100644
---
a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
+++
b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeHelper.java
@@ -689,12 +689,18 @@ public final class ExchangeHelper {
}
/**
- * Checks whether the exchange {@link UnitOfWork} is redelivered
+ * Checks whether the exchange {@link UnitOfWork} is redelivered: the
error handler redelivered it (its redelivery
+ * counter, which a route cannot remove), or the {@link
Exchange#REDELIVERED} header says so.
*
* @param exchange the exchange
* @return <tt>true</tt> if redelivered, <tt>false</tt> otherwise
*/
public static boolean isRedelivered(Exchange exchange) {
+ // the counter of the exchange first: a route that removes the Camel
headers after the redelivery (such as
+ // removeHeaders("Camel*") before calling an endpoint) takes the
header away (CAMEL-26712)
+ if (exchange.getExchangeExtension().getRedeliveryCounter() > 0) {
+ return true;
+ }
return exchange.getIn().hasHeaders() &&
exchange.getIn().getHeader(Exchange.REDELIVERED, false, Boolean.class);
}
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 db3944bfc278..2e2652c1fa71 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
@@ -241,6 +241,14 @@ at most once per millisecond, so that the JMX statistics
no longer generate an e
several exchanges complete within the same millisecond, the attribute has the
id of the first one, not of the last one.
Failed exchanges are not affected: `LastExchangeFailureExchangeId` is still
updated for every failed exchange.
+=== camel-management - an exchange recovered by a redelivery is counted when
the route removes the Camel headers
+
+The `Redeliveries` statistics of the routes, processors and the `CamelContext`
count an exchange that completed after a
+redelivery also when the route removed the `CamelRedelivered` header
afterwards (such as `removeHeaders("Camel*")`
+before calling an endpoint). Before, such a recovery was not counted.
`ExchangeHelper.isRedelivered` now also returns
+`true` when the error handler redelivered the exchange, without looking at the
header only. An exchange whose failure
+is continued (`continued(true)`) no longer keeps its redelivery counter, as
with a handled failure.
+
=== camel-core - a nested split no longer changes the processed items of the
outer split
With `errorThreshold` or `maxFailedRecords` on a split that contains another
split, the inner split changed the number of