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 45a165444069 CAMEL-26389: camel-core - RedeliveryErrorHandler: keep a
copy of the IN message for redelivery instead of the whole Exchange (#28648)
45a165444069 is described below
commit 45a165444069a225a0d91ac870588b801996fa7f
Author: Adriano Machado <[email protected]>
AuthorDate: Thu Oct 29 01:38:40 2026 -0400
CAMEL-26389: camel-core - RedeliveryErrorHandler: keep a copy of the IN
message for redelivery instead of the whole Exchange (#28648)
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
.../errorhandler/RedeliveryErrorHandler.java | 20 ++--
.../RedeliveryErrorHandlerCopyInMessageTest.java | 103 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_24.adoc | 12 +++
3 files changed, 127 insertions(+), 8 deletions(-)
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 99e655477a45..903cf73f14af 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
@@ -285,9 +285,12 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
/**
* Performs a defensive copy of the exchange if needed
*
- * @param exchange the exchange
- * @return the defensive copy, or <tt>null</tt> if not needed
(redelivery is not enabled).
+ * @param exchange the exchange
+ * @return the defensive copy, or <tt>null</tt> if not needed
(redelivery is not enabled).
+ * @deprecated since 4.24.0. No longer called: redelivery only
restores the IN message, so the error
+ * handler keeps a copy of the IN message instead of
the whole exchange.
*/
+ @Deprecated(since = "4.24.0")
protected Exchange defensiveCopyExchangeIfNeeded(Exchange exchange) {
// only do a defensive copy if redelivery is enabled
if (redeliveryEnabled) {
@@ -1045,7 +1048,7 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
*/
protected class RedeliveryTask implements PooledExchangeTask {
// state
- private Exchange original;
+ private Message original;
private Exchange exchange;
private AsyncCallback callback;
private int redeliveryCounter;
@@ -1073,9 +1076,10 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
@Override
public void prepare(Exchange exchange, AsyncCallback callback) {
useErrorHandlerDefaults();
- // do a defensive copy of the original Exchange, which is needed
for redelivery so we can ensure the
- // original Exchange is being redelivered, and not a mutated
Exchange
- this.original = redeliveryEnabled ?
defensiveCopyExchangeIfNeeded(exchange) : null;
+ // do a defensive copy of the original IN message, which is needed
for redelivery so we can ensure the
+ // original message is being redelivered, and not a mutated
message. Only the IN message is restored on
+ // redelivery, and this runs for every node on the success path,
so do not copy the whole Exchange
+ this.original = redeliveryEnabled ? exchange.getIn().copy() : null;
this.exchange = exchange;
this.callback = callback;
}
@@ -1414,7 +1418,7 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
+ ". Make sure you have
configured the error handler properly.");
}
// there must be a defensive copy of the exchange
- ObjectHelper.notNull(this.original, "Defensive copy of Exchange is
null", RedeliveryErrorHandler.this);
+ ObjectHelper.notNull(this.original, "Defensive copy of IN message
is null", RedeliveryErrorHandler.this);
// okay we will give it another go so clear the exception so we
can try again
exchange.setException(null);
@@ -1423,7 +1427,7 @@ public abstract class RedeliveryErrorHandler extends
ErrorHandlerSupport
exchange.setRollbackOnly(false);
// we are redelivering so copy from original back to exchange
- exchange.getIn().copyFrom(this.original.getIn());
+ exchange.getIn().copyFrom(this.original);
exchange.setOut(null);
// reset cached streams so they can be read again
MessageHelper.resetStreamCache(exchange.getIn());
diff --git
a/core/camel-core/src/test/java/org/apache/camel/processor/RedeliveryErrorHandlerCopyInMessageTest.java
b/core/camel-core/src/test/java/org/apache/camel/processor/RedeliveryErrorHandlerCopyInMessageTest.java
new file mode 100644
index 000000000000..15882824efdb
--- /dev/null
+++
b/core/camel-core/src/test/java/org/apache/camel/processor/RedeliveryErrorHandlerCopyInMessageTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.processor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.SafeCopyProperty;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+/**
+ * The redelivery error handler keeps a copy of the IN message for each node,
so it can redeliver the original message.
+ * It must not copy the whole exchange for that, as this happens for every
node on the success path.
+ */
+public class RedeliveryErrorHandlerCopyInMessageTest extends
ContextTestSupport {
+
+ private final AtomicInteger exchangeCopies = new AtomicInteger();
+ private final List<String> attempts = new ArrayList<>();
+
+ @Test
+ public void testSuccessPathDoesNotCopyExchange() {
+ Exchange out = template.send("direct:success", e ->
e.getIn().setBody("Hello World"));
+
+ assertNull(out.getException());
+ assertEquals("Hello World", out.getMessage().getBody());
+ // every copy of the exchange calls safeCopy on this property, so 0
means the nodes after it did not copy
+ assertEquals(0, exchangeCopies.get());
+ }
+
+ @Test
+ public void testRedeliveryRestoresOriginalInMessage() {
+ Exchange out = template.send("direct:redeliver", e -> {
+ e.getIn().setBody("Hello World");
+ e.getIn().setHeader("foo", "original");
+ });
+
+ assertNull(out.getException());
+ // each attempt sees the original body and header, not what the failed
attempt changed
+ assertEquals(List.of("Hello World/original", "Hello World/original",
"Hello World/original"), attempts);
+ assertEquals("Bye World", out.getMessage().getBody());
+ // exchange properties are not part of the message, so they are not
restored on redelivery
+ assertEquals(2, out.getProperty("failedAttempts", Integer.class));
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ return new RouteBuilder() {
+ @Override
+ public void configure() {
+
errorHandler(defaultErrorHandler().maximumRedeliveries(3).redeliveryDelay(0));
+
+ from("direct:success")
+ .process(e ->
e.getExchangeExtension().setSafeCopyProperty("counter", new CountingProperty()))
+ .setHeader("step", constant(1))
+ .setHeader("step", constant(2))
+ .process(e -> e.getIn().setHeader("step", 3));
+
+ from("direct:redeliver")
+ .process(e -> {
+ attempts.add(e.getIn().getBody() + "/" +
e.getIn().getHeader("foo"));
+ e.getIn().setBody("Changed");
+ e.getIn().setHeader("foo", "changed");
+ if (attempts.size() < 3) {
+ int failed = e.getProperty("failedAttempts",
0, Integer.class);
+ e.setProperty("failedAttempts", failed + 1);
+ throw new IllegalArgumentException("Forced
attempt " + attempts.size());
+ }
+ e.getIn().setBody("Bye World");
+ });
+ }
+ };
+ }
+
+ private final class CountingProperty implements SafeCopyProperty {
+
+ @Override
+ public SafeCopyProperty safeCopy() {
+ exchangeCopies.incrementAndGet();
+ return this;
+ }
+ }
+}
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 3f7d5727bcc6..b761304b66bb 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
@@ -100,6 +100,18 @@ in its model, so clearing them on the route saved little
memory at the cost of m
Applications that called `Route.clearRouteModel()` directly can remove the
call. Implementations of the `Route` interface
that override `clearRouteModel()` can remove the override.
+=== camel-core - RedeliveryErrorHandler.defensiveCopyExchangeIfNeeded is
deprecated
+
+When redelivery is enabled, the error handler (`DefaultErrorHandler`,
`DeadLetterChannel` and the transaction error
+handlers) keeps a copy of the message before each step of the route, so a
redelivery starts again from the original
+message. It copied the whole exchange for that, at every step, also when the
step succeeded, but a redelivery only ever
+restored the IN message from it. It now keeps a copy of the IN message only.
Routes behave as before: exchange
+properties and variables were not restored on redelivery, and still are not.
+
+The protected `RedeliveryErrorHandler.defensiveCopyExchangeIfNeeded(Exchange)`
method is deprecated and no longer called.
+A custom error handler that overrides it to change what is redelivered must
change the message before the redelivery
+instead, for example with `onRedelivery`.
+
=== camel-core - the documented default of skipBindingOnErrorCode is true
The `skipBindingOnErrorCode` option of the Rest DSL (on `restConfiguration`,
`rest`, its verbs and `restBinding`) is now