You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2024/03/23 13:34:00 UTC
(camel) branch main updated: Var fail (#13597)
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 41adc3a5988 Var fail (#13597)
41adc3a5988 is described below
commit 41adc3a59889ef7f32715d4fc16b8dbfcc891ced
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Mar 23 14:33:54 2024 +0100
Var fail (#13597)
CAMEL-20607: camel-core - Using variableReceive should only set result if exchange was process succesfully
---
.../java/org/apache/camel/processor/Enricher.java | 10 +-
.../org/apache/camel/processor/PollEnricher.java | 9 +-
.../camel/processor/SendDynamicProcessor.java | 8 +-
.../org/apache/camel/processor/SendProcessor.java | 14 +-
.../camel/processor/EnrichVariableErrorTest.java | 291 +++++++++++++++++++
.../processor/PollEnrichVariableErrorTest.java | 112 +++++++
.../processor/ToDynamicVariableErrorTest.java | 321 +++++++++++++++++++++
.../camel/processor/ToVariableErrorTest.java | 291 +++++++++++++++++++
.../org/apache/camel/support/ExchangeHelper.java | 23 ++
.../ROOT/pages/camel-4x-upgrade-guide-4_6.adoc | 16 +
.../modules/ROOT/pages/camel-4x-upgrade-guide.adoc | 1 +
11 files changed, 1075 insertions(+), 21 deletions(-)
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
index 5dcabc96fac..9424fe89a61 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/Enricher.java
@@ -222,12 +222,12 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
if (aggregatedExchange != null) {
- if (variableReceive != null) {
+ if (ExchangeHelper.shouldSetVariableResult(aggregatedExchange, variableReceive)) {
// result should be stored in variable instead of message body
- ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
- exchange.getMessage());
- exchange.getMessage().setBody(originalBody);
- exchange.getMessage().setHeaders(originalHeaders);
+ ExchangeHelper.setVariableFromMessageBodyAndHeaders(aggregatedExchange, variableReceive,
+ aggregatedExchange.getMessage());
+ aggregatedExchange.getMessage().setBody(originalBody);
+ aggregatedExchange.getMessage().setHeaders(originalHeaders);
}
// copy aggregation result onto original exchange (preserving pattern)
copyResultsWithoutCorrelationId(exchange, aggregatedExchange);
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java
index d939963dd26..e13ec57ed88 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/PollEnricher.java
@@ -350,11 +350,12 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
// must catch any exception from aggregation
Exchange aggregatedExchange = aggregationStrategy.aggregate(exchange, resourceExchange);
if (aggregatedExchange != null) {
- if (variableReceive != null) {
+ if (ExchangeHelper.shouldSetVariableResult(aggregatedExchange, variableReceive)) {
// result should be stored in variable instead of message body
- ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive, exchange.getMessage());
- exchange.getMessage().setBody(originalBody);
- exchange.getMessage().setHeaders(originalHeaders);
+ ExchangeHelper.setVariableFromMessageBodyAndHeaders(aggregatedExchange, variableReceive,
+ aggregatedExchange.getMessage());
+ aggregatedExchange.getMessage().setBody(originalBody);
+ aggregatedExchange.getMessage().setHeaders(originalHeaders);
}
// copy aggregation result onto original exchange (preserving pattern)
copyResultsPreservePattern(exchange, aggregatedExchange);
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
index d877f180684..decf336be57 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendDynamicProcessor.java
@@ -237,10 +237,10 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
ServiceHelper.stopAndShutdownService(endpoint);
}
// result should be stored in variable instead of message body
- if (variableReceive != null) {
- ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive, exchange.getMessage());
- exchange.getMessage().setBody(originalBody);
- exchange.getMessage().setHeaders(originalHeaders);
+ if (ExchangeHelper.shouldSetVariableResult(target, variableReceive)) {
+ ExchangeHelper.setVariableFromMessageBodyAndHeaders(target, variableReceive, target.getMessage());
+ target.getMessage().setBody(originalBody);
+ target.getMessage().setHeaders(originalHeaders);
}
// signal we are done
c.done(doneSync);
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
index 04840285980..37dd575fccd 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SendProcessor.java
@@ -179,11 +179,11 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
ac = doneSync -> {
try {
// result should be stored in variable instead of message body/headers
- if (variableReceive != null) {
- ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
- exchange.getMessage());
- exchange.getMessage().setBody(originalBody);
- exchange.getMessage().setHeaders(originalHeaders);
+ if (ExchangeHelper.shouldSetVariableResult(target, variableReceive)) {
+ ExchangeHelper.setVariableFromMessageBodyAndHeaders(target, variableReceive,
+ target.getMessage());
+ target.getMessage().setBody(originalBody);
+ target.getMessage().setHeaders(originalHeaders);
}
// restore previous MEP
target.setPattern(existingPattern);
@@ -202,8 +202,6 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
if (variableSend != null) {
Object value = ExchangeHelper.getVariable(exchange, variableSend);
exchange.getMessage().setBody(value);
- // TODO: empty headers or
-
}
LOG.debug(">>>> {} {}", destination, exchange);
@@ -240,7 +238,7 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
// restore previous MEP
exchange.setPattern(existingPattern);
// result should be stored in variable instead of message body/headers
- if (variableReceive != null) {
+ if (ExchangeHelper.shouldSetVariableResult(exchange, variableReceive)) {
ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
exchange.getMessage());
exchange.getMessage().setBody(originalBody);
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableErrorTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableErrorTest.java
new file mode 100644
index 00000000000..3c199057eae
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableErrorTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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 org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class EnrichVariableErrorTest extends ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @Test
+ public void testThrowException() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ // TODO: should this be World or Bye World?
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testTryCatch() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .doTry()
+ .throwException(new IllegalArgumentException("Forced"))
+ .doCatch(Exception.class)
+ .setBody(simple("Catch: ${body}"))
+ .end();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(1);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertTrue(out.hasVariables());
+ Assertions.assertEquals("World", out.getMessage().getBody());
+ Assertions.assertEquals("Catch: Bye World", out.getVariable("bye"));
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testOnExceptionHandled() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ onException(Exception.class)
+ .handled(true)
+ .setBody(simple("Error: ${body}"));
+
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Error: Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testOnExceptionNotHandled() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ onException(Exception.class)
+ .handled(false)
+ .setBody(simple("Error: ${body}"));
+
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Error: Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testDeadLetterChannel() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ errorHandler(deadLetterChannel("mock:dead"));
+
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ getMockEndpoint("mock:dead").expectedMessageCount(1);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testDefaultErrorHandler() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ errorHandler(defaultErrorHandler());
+
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .stop();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testRollback() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .rollback();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testMarkRollbackLast() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .markRollbackOnly();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testMarkRollbackOnlyLast() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .enrich().constant("direct:foo").variableReceive("bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .markRollbackOnlyLast();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableErrorTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableErrorTest.java
new file mode 100644
index 00000000000..c1dd22ff715
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableErrorTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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 org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.seda.SedaEndpoint;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PollEnrichVariableErrorTest extends ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @Test
+ public void testThrowException() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .pollEnrich().constant("seda:foo").timeout(1000).variableReceive("bye")
+ .to("mock:result");
+ }
+ });
+ context.start();
+
+ template.send("seda:foo", e -> {
+ e.getMessage().setBody("Bye World");
+ e.setException(new IllegalArgumentException());
+ });
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .pollEnrich().constant("seda:foo").timeout(1000).variableReceive("bye")
+ .to("mock:result");
+ }
+ });
+ context.start();
+
+ SedaEndpoint se = context.getEndpoint("seda:foo", SedaEndpoint.class);
+ Exchange ex = se.createExchange();
+ ex.getMessage().setBody("Bye World");
+ ex.setRouteStop(true);
+ se.getQueue().add(ex);
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertTrue(out.isRouteStop());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testRollbackOnly() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .pollEnrich().constant("seda:foo").timeout(1000).variableReceive("bye")
+ .to("mock:result");
+ }
+ });
+ context.start();
+
+ SedaEndpoint se = context.getEndpoint("seda:foo", SedaEndpoint.class);
+ Exchange ex = se.createExchange();
+ ex.getMessage().setBody("Bye World");
+ ex.setRollbackOnly(true);
+ se.getQueue().add(ex);
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertTrue(out.isRollbackOnly());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableErrorTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableErrorTest.java
new file mode 100644
index 00000000000..00bc2a10fe4
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableErrorTest.java
@@ -0,0 +1,321 @@
+/*
+ * 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 org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ToDynamicVariableErrorTest extends ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @Test
+ public void testThrowException() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ // TODO: should this be World or Bye World?
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testTryCatch() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .doTry()
+ .throwException(new IllegalArgumentException("Forced"))
+ .doCatch(Exception.class)
+ .setBody(simple("Catch: ${body}"))
+ .end();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(1);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertTrue(out.hasVariables());
+ Assertions.assertEquals("World", out.getMessage().getBody());
+ Assertions.assertEquals("Catch: Bye World", out.getVariable("bye"));
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testOnExceptionHandled() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ onException(Exception.class)
+ .handled(true)
+ .setBody(simple("Error: ${body}"));
+
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Error: Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testOnExceptionNotHandled() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ onException(Exception.class)
+ .handled(false)
+ .setBody(simple("Error: ${body}"));
+
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Error: Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testDeadLetterChannel() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ errorHandler(deadLetterChannel("mock:dead"));
+
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ getMockEndpoint("mock:dead").expectedMessageCount(1);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testDefaultErrorHandler() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ errorHandler(defaultErrorHandler());
+
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .stop();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testRollback() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .rollback();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testMarkRollbackLast() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .markRollbackOnly();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testMarkRollbackOnlyLast() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toD("direct:${header.where}", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .markRollbackOnlyLast();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> {
+ e.getMessage().setHeader("where", "foo");
+ e.getMessage().setBody("World");
+ });
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableErrorTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableErrorTest.java
new file mode 100644
index 00000000000..f3351a2941d
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableErrorTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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 org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ToVariableErrorTest extends ContextTestSupport {
+
+ @Override
+ public boolean isUseRouteBuilder() {
+ return false;
+ }
+
+ @Test
+ public void testThrowException() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ // TODO: should this be World or Bye World?
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testTryCatch() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .doTry()
+ .throwException(new IllegalArgumentException("Forced"))
+ .doCatch(Exception.class)
+ .setBody(simple("Catch: ${body}"))
+ .end();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(1);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertTrue(out.hasVariables());
+ Assertions.assertEquals("World", out.getMessage().getBody());
+ Assertions.assertEquals("Catch: Bye World", out.getVariable("bye"));
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testOnExceptionHandled() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ onException(Exception.class)
+ .handled(true)
+ .setBody(simple("Error: ${body}"));
+
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Error: Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testOnExceptionNotHandled() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ onException(Exception.class)
+ .handled(false)
+ .setBody(simple("Error: ${body}"));
+
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Error: Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testDeadLetterChannel() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ errorHandler(deadLetterChannel("mock:dead"));
+
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ getMockEndpoint("mock:dead").expectedMessageCount(1);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testDefaultErrorHandler() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ errorHandler(defaultErrorHandler());
+
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .throwException(new IllegalArgumentException("Forced"));
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testStop() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .stop();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testRollback() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .rollback();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertTrue(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testMarkRollbackLast() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .markRollbackOnly();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testMarkRollbackOnlyLast() throws Exception {
+ context.addRoutes(new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ from("direct:receive")
+ .toV("direct:foo", null, "bye")
+ .to("mock:result");
+
+ from("direct:foo")
+ .transform().simple("Bye ${body}")
+ .markRollbackOnlyLast();
+ }
+ });
+ context.start();
+
+ getMockEndpoint("mock:result").expectedMessageCount(0);
+ Exchange out = template.request("direct:receive", e -> e.getMessage().setBody("World"));
+ Assertions.assertFalse(out.isFailed());
+ Assertions.assertFalse(out.hasVariables());
+ Assertions.assertEquals("Bye World", out.getMessage().getBody());
+ assertMockEndpointsSatisfied();
+ }
+
+}
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 2cdf7071450..a73db916db9 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
@@ -1156,6 +1156,29 @@ public final class ExchangeHelper {
}
}
+ /**
+ * Whether the processing of the {@link Exchange} was success and that the result should be stored in variable.
+ *
+ * @param exchange the exchange
+ * @param name the variable name
+ * @return true to call setVariableFromMessageBodyAndHeaders to set the result after-wards
+ */
+ public static boolean shouldSetVariableResult(Exchange exchange, String name) {
+ if (name == null) {
+ return false;
+ }
+ // same logic as in Pipeline/PipelineHelper
+ boolean stop
+ = exchange.isRouteStop() || exchange.isFailed() || exchange.isRollbackOnly() || exchange.isRollbackOnlyLast()
+ || exchange.getExchangeExtension().isErrorHandlerHandledSet()
+ && exchange.getExchangeExtension().isErrorHandlerHandled();
+ if (stop) {
+ return false;
+ }
+ // success
+ return true;
+ }
+
/**
* Gets the variable
*
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_6.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_6.adoc
new file mode 100644
index 00000000000..d297df9c99e
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_6.adoc
@@ -0,0 +1,16 @@
+= Apache Camel 4.x Upgrade Guide
+
+This document is for helping you upgrade your Apache Camel application
+from Camel 4.x to 4.y. For example, if you are upgrading Camel 4.0 to 4.2, then you should follow the guides
+from both 4.0 to 4.1 and 4.1 to 4.2.
+
+== Upgrading Camel 4.5 to 4.6
+
+=== variables
+
+When using `variableReceive` then the variable is only set if processing the `Exchange` was completely successfully.
+
+For example calling a route that fails due to an exception being thrown (even if `onException` or `errorHandler` are in use)
+then the variable is no longer set. Also, if the route is marked for rollback, or to stop continue routing with `.stop()`.
+
+This is the same logic that the routing engine uses, whether to continue routing the `Exchange` or not.
diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide.adoc
index d82ec3704e4..903ec6b126e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide.adoc
@@ -14,4 +14,5 @@ You can find upgrade guide for each release in the following pages:
- xref:camel-4x-upgrade-guide-4_3.adoc[Upgrade guide 4.2 -> 4.3]
- xref:camel-4x-upgrade-guide-4_4.adoc[Upgrade guide 4.3 -> 4.4]
- xref:camel-4x-upgrade-guide-4_5.adoc[Upgrade guide 4.4 -> 4.5]
+- xref:camel-4x-upgrade-guide-4_5.adoc[Upgrade guide 4.5 -> 4.6]