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

Reply via email to