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/01/23 15:29:57 UTC

(camel) 07/19: CAMEL-19749: EIPs should make it easy to use together with variables.

This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch var
in repository https://gitbox.apache.org/repos/asf/camel.git

commit aeb54da09abce4eb25311ab995a0577130ce3fbc
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Sat Jan 20 15:47:53 2024 +0100

    CAMEL-19749: EIPs should make it easy to use together with variables.
---
 .../org/apache/camel/catalog/models/to.json        |   6 +-
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  29 ++-
 .../spring/processor/SpringToVariableTest.java     |  29 +++
 .../camel/spring/processor/ToVariableTest.xml      |  66 ++++++
 .../org/apache/camel/model/FromDefinition.java     |   4 +-
 .../apache/camel/model/ProcessorDefinition.java    |   8 +-
 .../org/apache/camel/model/RouteDefinition.java    |  20 +-
 .../org/apache/camel/model/RoutesDefinition.java   |  20 +-
 .../java/org/apache/camel/model/ToDefinition.java  |  19 +-
 .../org/apache/camel/processor/SendProcessor.java  |  43 +++-
 .../camel/processor/SetVariableProcessor.java      |  25 +-
 .../org/apache/camel/reifier/RouteReifier.java     |   3 +-
 .../bean/BeanCreateBodyExceptionTest.java          |   4 +-
 .../org/apache/camel/processor/ToVariableTest.java |  44 +++-
 .../org/apache/camel/support/ExchangeHelper.java   |  45 ++++
 .../dsl/yaml/deserializers/ModelDeserializers.java |   4 +-
 .../generated/resources/schema/camelYamlDsl.json   |   8 +-
 .../apache/camel/dsl/yaml/ToVariableTest.groovy    | 255 +++++++++++++++++++++
 18 files changed, 557 insertions(+), 75 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
index 053e6c95045..8f8698291c4 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/to.json
@@ -15,7 +15,9 @@
     "id": { "index": 0, "kind": "attribute", "displayName": "Id", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the id of this node" },
     "description": { "index": 1, "kind": "element", "displayName": "Description", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the description of this node" },
     "disabled": { "index": 2, "kind": "attribute", "displayName": "Disabled", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to disable this EIP from the route during build time. Once an EIP has been disabled then it cannot be enabled later at runtime." },
-    "uri": { "index": 3, "kind": "attribute", "displayName": "Uri", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the uri of the endpoint to send to." },
-    "pattern": { "index": 4, "kind": "attribute", "displayName": "Pattern", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the optional ExchangePattern used to invoke this endpoint" }
+    "variableSend": { "index": 3, "kind": "attribute", "displayName": "Variable Send", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "To use a variable as the source for the message body to send. This makes it handy to use variables for user data and to easily control what data to use for sending and receiving. Important: When using send variable then the message body is taken from this variab [...]
+    "variableReceive": { "index": 4, "kind": "attribute", "displayName": "Variable Receive", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "To use a variable to store the received message body (only body, not headers). This is handy for easy access to the received message body via variables. Important: When using receive variable then the received body is stored only in this variable and not o [...]
+    "uri": { "index": 5, "kind": "attribute", "displayName": "Uri", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the uri of the endpoint to send to." },
+    "pattern": { "index": 6, "kind": "attribute", "displayName": "Pattern", "label": "advanced", "required": false, "type": "enum", "javaType": "org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], "deprecated": false, "autowired": false, "secret": false, "description": "Sets the optional ExchangePattern used to invoke this endpoint" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
index 868efc3a0f3..a465a963fc8 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/schemas/camel-spring.xsd
@@ -6096,12 +6096,12 @@ Sets the URI of the endpoint to use.
             </xs:documentation>
           </xs:annotation>
         </xs:attribute>
-        <xs:attribute name="variable" type="xs:string">
+        <xs:attribute name="variableReceive" type="xs:string">
           <xs:annotation>
             <xs:documentation xml:lang="en">
 <![CDATA[
-To use a variable to store a copy of the incoming message body (only body, not headers). This is handy for easy access
-to the incoming message body via variables.
+To use a variable to store a copy of the received message body (only body, not headers). This is handy for easy access
+to the received message body via variables.
 ]]>
             </xs:documentation>
           </xs:annotation>
@@ -13524,6 +13524,29 @@ Reference to the exception instance to lookup from the registry to throw.
     <xs:complexContent>
       <xs:extension base="tns:sendDefinition">
         <xs:sequence/>
+        <xs:attribute name="variableSend" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en">
+<![CDATA[
+To use a variable as the source for the message body to send. This makes it handy to use variables for user data and to
+easily control what data to use for sending and receiving. Important: When using send variable then the message body is
+taken from this variable instead of the current Message , however the headers from the Message will still be used as
+well. In other words, the variable is used instead of the message body, but everything else is as usual.
+]]>
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="variableReceive" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en">
+<![CDATA[
+To use a variable to store the received message body (only body, not headers). This is handy for easy access to the
+received message body via variables. Important: When using receive variable then the received body is stored only in
+this variable and not on the current org.apache.camel.Message .
+]]>
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
         <xs:attribute name="pattern" type="xs:string">
           <xs:annotation>
             <xs:documentation xml:lang="en">
diff --git a/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringToVariableTest.java b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringToVariableTest.java
new file mode 100644
index 00000000000..644fc0894e0
--- /dev/null
+++ b/components/camel-spring-xml/src/test/java/org/apache/camel/spring/processor/SpringToVariableTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.spring.processor;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.processor.ToVariableTest;
+
+import static org.apache.camel.spring.processor.SpringTestHelper.createSpringCamelContext;
+
+public class SpringToVariableTest extends ToVariableTest {
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        return createSpringCamelContext(this, "org/apache/camel/spring/processor/ToVariableTest.xml");
+    }
+}
diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/ToVariableTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/ToVariableTest.xml
new file mode 100644
index 00000000000..0c23f7592b3
--- /dev/null
+++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/ToVariableTest.xml
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<beans xmlns="http://www.springframework.org/schema/beans"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:schemaLocation="
+       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+       http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd
+    ">
+
+  <!-- START SNIPPET: example -->
+  <camelContext xmlns="http://camel.apache.org/schema/spring">
+    <jmxAgent id="jmx" disabled="true"/>
+    <route>
+      <from uri="direct:send"/>
+      <setVariable name="hello">
+        <simple>Camel</simple>
+      </setVariable>
+      <to uri="mock:before"/>
+      <to uri="direct:foo" variableSend="hello"/>
+      <to uri="mock:result"/>
+    </route>
+    <route>
+      <from uri="direct:receive"/>
+      <to uri="direct:foo" variableReceive="bye"/>
+      <to uri="mock:after"/>
+      <setBody>
+        <simple>${variable:bye}</simple>
+      </setBody>
+      <to uri="mock:result"/>
+    </route>
+    <route>
+      <from uri="direct:sendAndReceive"/>
+      <setVariable name="hello">
+        <simple>Camel</simple>
+      </setVariable>
+      <to uri="mock:before"/>
+      <to uri="direct:foo" variableSend="hello" variableReceive="bye"/>
+      <to uri="mock:result"/>
+    </route>
+    <route>
+      <from uri="direct:foo"/>
+      <transform>
+        <simple>Bye ${body}</simple>
+      </transform>
+    </route>
+  </camelContext>
+  <!-- END SNIPPET: example -->
+
+</beans>
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
index c23893241cf..dc5d4b95665 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/FromDefinition.java
@@ -126,8 +126,8 @@ public class FromDefinition extends OptionalIdentifiedDefinition<FromDefinition>
     }
 
     /**
-     * To use a variable to store a copy of the received message body (only body, not headers).
-     * This is handy for easy access to the received message body via variables.
+     * To use a variable to store a copy of the received message body (only body, not headers). This is handy for easy
+     * access to the received message body via variables.
      */
     public void setVariableReceive(String variableReceive) {
         this.variableReceive = variableReceive;
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
index b4bf7f5605f..5d6e933a95b 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
@@ -258,10 +258,10 @@ public abstract class ProcessorDefinition<Type extends ProcessorDefinition<Type>
     /**
      * Sends the exchange to the given endpoint
      *
-     * @param  uri the endpoint to send to
-     * @param variableSend to use a variable as the source for the message body to send.
-     * @param variableReceive to use a variable to store the received message body (only body, not headers).
-     * @return     the builder
+     * @param  uri             the endpoint to send to
+     * @param  variableSend    to use a variable as the source for the message body to send.
+     * @param  variableReceive to use a variable to store the received message body (only body, not headers).
+     * @return                 the builder
      */
     public Type toV(@AsEndpointUri String uri, String variableSend, String variableReceive) {
         ToDefinition to = new ToDefinition(uri);
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
index c62560e9259..7b274a5ef1c 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RouteDefinition.java
@@ -218,12 +218,12 @@ public class RouteDefinition extends OutputDefinition<RouteDefinition>
     }
 
     /**
-     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not headers).
-     * This is handy for easy access to the received message body via variables.
+     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not
+     * headers). This is handy for easy access to the received message body via variables.
      *
-     * @param  uri            the from uri
-     * @param variableReceive the name of the variable
-     * @return                the builder
+     * @param  uri             the from uri
+     * @param  variableReceive the name of the variable
+     * @return                 the builder
      */
     public RouteDefinition fromV(@AsEndpointUri String uri, String variableReceive) {
         FromDefinition from = new FromDefinition(uri);
@@ -233,12 +233,12 @@ public class RouteDefinition extends OutputDefinition<RouteDefinition>
     }
 
     /**
-     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not headers).
-     * This is handy for easy access to the received message body via variables.
+     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not
+     * headers). This is handy for easy access to the received message body via variables.
      *
-     * @param  endpoint       the from endpoint
-     * @param variableReceive the name of the variable
-     * @return                the builder
+     * @param  endpoint        the from endpoint
+     * @param  variableReceive the name of the variable
+     * @return                 the builder
      */
     public RouteDefinition fromV(EndpointConsumerBuilder endpoint, String variableReceive) {
         FromDefinition from = new FromDefinition(endpoint);
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
index 81b013e4d0b..f67b702dabb 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/RoutesDefinition.java
@@ -197,12 +197,12 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit
     }
 
     /**
-     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not headers).
-     * This is handy for easy access to the received message body via variables.
+     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not
+     * headers). This is handy for easy access to the received message body via variables.
      *
-     * @param uri             the from uri
-     * @param variableReceive the name of the variable
-     * @return                the builder
+     * @param  uri             the from uri
+     * @param  variableReceive the name of the variable
+     * @return                 the builder
      */
     public RouteDefinition fromV(@AsEndpointUri String uri, String variableReceive) {
         RouteDefinition route = createRoute();
@@ -235,12 +235,12 @@ public class RoutesDefinition extends OptionalIdentifiedDefinition<RoutesDefinit
     }
 
     /**
-     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not headers).
-     * This is handy for easy access to the received message body via variables.
+     * Creates an input to the route, and uses a variable to store a copy of the received message body (only body, not
+     * headers). This is handy for easy access to the received message body via variables.
      *
-     * @param  endpoint       the from endpoint
-     * @param variableReceive the name of the variable
-     * @return                the builder
+     * @param  endpoint        the from endpoint
+     * @param  variableReceive the name of the variable
+     * @return                 the builder
      */
     public RouteDefinition fromV(EndpointConsumerBuilder endpoint, String variableReceive) {
         RouteDefinition route = createRoute();
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
index 4b4864e59c8..fed74435cd4 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/ToDefinition.java
@@ -103,13 +103,12 @@ public class ToDefinition extends SendDefinition<ToDefinition> {
     }
 
     /**
-     * To use a variable as the source for the message body to send.
-     * This makes it handy to use variables for user data and to easily control what data to use for sending and receiving.
+     * To use a variable as the source for the message body to send. This makes it handy to use variables for user data
+     * and to easily control what data to use for sending and receiving.
      *
-     * Important: When using send variable then the message body
-     * is taken from this variable instead of the current {@link Message}, however
-     * the headers from the {@link Message} will still be used as well. In other words,
-     * the variable is used instead of the message body, but everything else is as usual.
+     * Important: When using send variable then the message body is taken from this variable instead of the current
+     * {@link Message}, however the headers from the {@link Message} will still be used as well. In other words, the
+     * variable is used instead of the message body, but everything else is as usual.
      */
     public void setVariableSend(String variableSend) {
         this.variableSend = variableSend;
@@ -120,11 +119,11 @@ public class ToDefinition extends SendDefinition<ToDefinition> {
     }
 
     /**
-     * To use a variable to store the received message body (only body, not headers).
-     * This is handy for easy access to the received message body via variables.
+     * To use a variable to store the received message body (only body, not headers). This is handy for easy access to
+     * the received message body via variables.
      *
-     * Important: When using receive variable then the received body is stored
-     * only in this variable and <b>not</b> on the current {@link org.apache.camel.Message}.
+     * Important: When using receive variable then the received body is stored only in this variable and <b>not</b> on
+     * the current {@link org.apache.camel.Message}.
      */
     public void setVariableReceive(String variableReceive) {
         this.variableReceive = variableReceive;
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 326f103a4cc..960450d8d44 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
@@ -33,6 +33,7 @@ import org.apache.camel.spi.RouteIdAware;
 import org.apache.camel.support.AsyncProcessorSupport;
 import org.apache.camel.support.EndpointHelper;
 import org.apache.camel.support.EventHelper;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.cache.DefaultProducerCache;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.ObjectHelper;
@@ -127,6 +128,20 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
         // if you want to permanently to change the MEP then use .setExchangePattern in the DSL
         final ExchangePattern existingPattern = exchange.getPattern();
 
+        // if we should store the received message body in a variable,
+        // then we need to preserve the original message body
+        Object body = null;
+        if (variableReceive != null) {
+            try {
+                body = exchange.getMessage().getBody();
+            } catch (Exception throwable) {
+                exchange.setException(throwable);
+                callback.done(true);
+                return true;
+            }
+        }
+        final Object originalBody = body;
+
         if (extendedStatistics) {
             counter.incrementAndGet();
         }
@@ -153,10 +168,16 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
 
             // optimize to only create a new callback if really needed, otherwise we can use the provided callback as-is
             AsyncCallback ac = callback;
-            boolean newCallback = watch != null || existingPattern != target.getPattern();
+            boolean newCallback = watch != null || existingPattern != target.getPattern() || variableReceive != null;
             if (newCallback) {
                 ac = doneSync -> {
                     try {
+                        // result should be stored in variable instead of message body
+                        if (variableReceive != null) {
+                            Object value = exchange.getMessage().getBody();
+                            ExchangeHelper.setVariable(exchange, variableReceive, value);
+                            exchange.getMessage().setBody(originalBody);
+                        }
                         // restore previous MEP
                         target.setPattern(existingPattern);
                         // emit event that the exchange was sent to the endpoint
@@ -170,6 +191,13 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
                 };
             }
             try {
+                // replace message body with variable
+                if (variableSend != null) {
+                    // it may be a global variable
+                    Object value = ExchangeHelper.getVariable(exchange, variableSend);
+                    exchange.getMessage().setBody(value);
+                }
+
                 LOG.debug(">>>> {} {}", destination, exchange);
                 boolean sync = producer.process(exchange, ac);
                 if (!sync) {
@@ -190,6 +218,13 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             // set property which endpoint we send to
             exchange.setProperty(ExchangePropertyKey.TO_ENDPOINT, destination.getEndpointUri());
 
+            // replace message body with variable
+            if (variableSend != null) {
+                // it may be a global variable
+                Object value = ExchangeHelper.getVariable(exchange, variableSend);
+                exchange.getMessage().setBody(value);
+            }
+
             LOG.debug(">>>> {} {}", destination, exchange);
 
             // send the exchange to the destination using the producer cache for the non optimized producers
@@ -197,6 +232,12 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
                     (producer, ex, cb) -> producer.process(ex, doneSync -> {
                         // restore previous MEP
                         exchange.setPattern(existingPattern);
+                        // result should be stored in variable instead of message body
+                        if (variableReceive != null) {
+                            Object value = exchange.getMessage().getBody();
+                            ExchangeHelper.setVariable(exchange, variableReceive, value);
+                            exchange.getMessage().setBody(originalBody);
+                        }
                         // signal we are done
                         cb.done(doneSync);
                     }));
diff --git a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java
index 85acea646bf..b0e73f75617 100644
--- a/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java
+++ b/core/camel-core-processor/src/main/java/org/apache/camel/processor/SetVariableProcessor.java
@@ -24,11 +24,9 @@ import org.apache.camel.Expression;
 import org.apache.camel.Traceable;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.RouteIdAware;
-import org.apache.camel.spi.VariableRepository;
-import org.apache.camel.spi.VariableRepositoryFactory;
 import org.apache.camel.support.AsyncProcessorSupport;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.util.ObjectHelper;
-import org.apache.camel.util.StringHelper;
 
 /**
  * A processor which sets the variable with an {@link Expression}
@@ -40,7 +38,6 @@ public class SetVariableProcessor extends AsyncProcessorSupport implements Trace
     private String routeId;
     private final Expression variableName;
     private final Expression expression;
-    private VariableRepositoryFactory factory;
 
     public SetVariableProcessor(Expression variableName, Expression expression) {
         this.variableName = variableName;
@@ -71,19 +68,7 @@ public class SetVariableProcessor extends AsyncProcessorSupport implements Trace
             }
 
             String key = variableName.evaluate(exchange, String.class);
-            String id = StringHelper.before(key, ":");
-            if (id != null) {
-                VariableRepository repo = factory.getVariableRepository(id);
-                if (repo != null) {
-                    key = StringHelper.after(key, ":");
-                    repo.setVariable(key, newVariable);
-                } else {
-                    exchange.setException(
-                            new IllegalArgumentException("VariableRepository with id: " + id + " does not exist"));
-                }
-            } else {
-                exchange.setVariable(key, newVariable);
-            }
+            ExchangeHelper.setVariable(exchange, key, newVariable);
         } catch (Exception e) {
             exchange.setException(e);
         }
@@ -92,12 +77,6 @@ public class SetVariableProcessor extends AsyncProcessorSupport implements Trace
         return true;
     }
 
-    @Override
-    protected void doBuild() throws Exception {
-        super.doBuild();
-        factory = getCamelContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
-    }
-
     @Override
     public String toString() {
         return id;
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
index 025497ace88..97a8b686a02 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/RouteReifier.java
@@ -51,6 +51,7 @@ import org.apache.camel.spi.ManagementInterceptStrategy;
 import org.apache.camel.spi.NodeIdFactory;
 import org.apache.camel.spi.RoutePolicy;
 import org.apache.camel.spi.RoutePolicyFactory;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.PluginHelper;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -434,7 +435,7 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
         @Override
         public Object before(Exchange exchange) throws Exception {
             Object body = exchange.getMessage().getBody();
-            exchange.setVariable(name, body);
+            ExchangeHelper.setVariable(exchange, name, body);
             return null;
         }
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanCreateBodyExceptionTest.java b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanCreateBodyExceptionTest.java
index 57d295e82ed..9ef9970c903 100644
--- a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanCreateBodyExceptionTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanCreateBodyExceptionTest.java
@@ -118,14 +118,14 @@ public class BeanCreateBodyExceptionTest extends ContextTestSupport {
         fail.set(true);
 
         Exception e = assertThrows(Exception.class,
-                () -> consumer.receiveBody("seda:empty", 10000),
+                () -> consumer.receiveBody("seda:empty", 1000),
                 "Should throw exception");
 
         assertIsInstanceOf(IllegalArgumentException.class, e);
         assertEquals("Forced internal error", e.getMessage());
 
         fail.set(false);
-        assertDoesNotThrow(() -> consumer.receiveBody("seda:empty", 10000), "");
+        assertDoesNotThrow(() -> consumer.receiveBody("seda:empty", 1000), "");
     }
 
     @Override
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableTest.java
index 561ca0a06b0..b5fbc29565b 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableTest.java
@@ -23,11 +23,37 @@ import org.junit.jupiter.api.Test;
 public class ToVariableTest extends ContextTestSupport {
 
     @Test
-    public void testOriginalBody() throws Exception {
+    public void testSend() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye Camel");
+        getMockEndpoint("mock:result").expectedVariableReceived("hello", "Camel");
+
+        template.sendBody("direct:send", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testReceive() throws Exception {
         getMockEndpoint("mock:after").expectedBodiesReceived("World");
+        getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+
+        template.sendBody("direct:receive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testSendAndReceive() throws Exception {
+        getMockEndpoint("mock:before").expectedBodiesReceived("World");
+        getMockEndpoint("mock:before").expectedVariableReceived("hello", "Camel");
         getMockEndpoint("mock:result").expectedBodiesReceived("World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye Camel");
 
-        template.sendBody("direct:start", "World");
+        template.sendBody("direct:sendAndReceive", "World");
 
         assertMockEndpointsSatisfied();
     }
@@ -37,12 +63,24 @@ public class ToVariableTest extends ContextTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
-                from("direct:start")
+                from("direct:send")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toV("direct:foo", "hello", null)
+                        .to("mock:result");
+
+                from("direct:receive")
                         .toV("direct:foo", null, "bye")
                         .to("mock:after")
                         .setBody(simple("${variable:bye}"))
                         .to("mock:result");
 
+                from("direct:sendAndReceive")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toV("direct:foo", "hello", "bye")
+                        .to("mock:result");
+
                 from("direct:foo")
                         .transform().simple("Bye ${body}");
             }
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 b1097243892..f1bd96981c1 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
@@ -1100,4 +1100,49 @@ public final class ExchangeHelper {
             exchange.setVariable(name, value);
         }
     }
+
+    /**
+     * Gets the variable
+     *
+     * @param  exchange the exchange
+     * @param  name     the variable name. Can be prefixed with repo-id:name to lookup the variable from a specific
+     *                  repository. If no repo-id is provided, then the variable is set on the exchange
+     * @return          the variable
+     */
+    public static Object getVariable(Exchange exchange, String name) {
+        Object answer;
+        String id = StringHelper.before(name, ":");
+        if (id != null) {
+            VariableRepositoryFactory factory
+                    = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
+            VariableRepository repo = factory.getVariableRepository(id);
+            if (repo != null) {
+                name = StringHelper.after(name, ":");
+                answer = repo.getVariable(name);
+            } else {
+                throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
+            }
+        } else {
+            answer = exchange.getVariable(name);
+        }
+        return answer;
+    }
+
+    /**
+     * Gets the variable, converted to the given type
+     *
+     * @param  exchange the exchange
+     * @param  name     the variable name. Can be prefixed with repo-id:name to lookup the variable from a specific
+     *                  repository. If no repo-id is provided, then the variable is set on the exchange
+     * @param  type     the type to convert to
+     * @return          the variable
+     */
+    public static <T> T getVariable(Exchange exchange, String name, Class<T> type) {
+        Object answer = getVariable(exchange, name);
+        if (answer != null) {
+            return exchange.getContext().getTypeConverter().convertTo(type, exchange, answer);
+        }
+        return null;
+    }
+
 }
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index aea4c113504..cab2a31da16 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -18051,8 +18051,8 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "parameters", type = "object"),
                     @YamlProperty(name = "pattern", type = "enum:InOnly,InOut", description = "Sets the optional ExchangePattern used to invoke this endpoint", displayName = "Pattern"),
                     @YamlProperty(name = "uri", type = "string", required = true, description = "Sets the uri of the endpoint to send to.", displayName = "Uri"),
-                    @YamlProperty(name = "variableReceive", type = "string"),
-                    @YamlProperty(name = "variableSend", type = "string")
+                    @YamlProperty(name = "variableReceive", type = "string", description = "To use a variable to store the received message body (only body, not headers). This is handy for easy access to the received message body via variables. Important: When using receive variable then the received body is stored only in this variable and not on the current org.apache.camel.Message .", displayName = "Variable Receive"),
+                    @YamlProperty(name = "variableSend", type = "string", description = "To use a variable as the source for the message body to send. This makes it handy to use variables for user data and to easily control what data to use for sending and receiving. Important: When using send variable then the message body is taken from this variable instead of the current Message , however the headers from the Message will still be used as well. In other words, the variable is used ins [...]
             }
     )
     public static class ToDefinitionDeserializer extends YamlDeserializerEndpointAwareBase<ToDefinition> {
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
index 87797184ab2..6089a141ca4 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/schema/camelYamlDsl.json
@@ -6851,10 +6851,14 @@
               "description" : "Sets the uri of the endpoint to send to."
             },
             "variableReceive" : {
-              "type" : "string"
+              "type" : "string",
+              "title" : "Variable Receive",
+              "description" : "To use a variable to store the received message body (only body, not headers). This is handy for easy access to the received message body via variables. Important: When using receive variable then the received body is stored only in this variable and not on the current org.apache.camel.Message ."
             },
             "variableSend" : {
-              "type" : "string"
+              "type" : "string",
+              "title" : "Variable Send",
+              "description" : "To use a variable as the source for the message body to send. This makes it handy to use variables for user data and to easily control what data to use for sending and receiving. Important: When using send variable then the message body is taken from this variable instead of the current Message , however the headers from the Message will still be used as well. In other words, the variable is used instead of the message body, but everything else is as usual."
             }
           }
         } ],
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ToVariableTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ToVariableTest.groovy
new file mode 100644
index 00000000000..e348a488cc2
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/ToVariableTest.groovy
@@ -0,0 +1,255 @@
+/*
+ * 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.dsl.yaml
+
+import org.apache.camel.component.mock.MockEndpoint
+import org.apache.camel.dsl.yaml.support.YamlTestSupport
+
+class ToVariableTest extends YamlTestSupport {
+
+    def "toVariable send"() {
+        setup:
+            loadRoutes '''
+                - route:
+                    from:
+                      uri: direct:send
+                      steps:
+                        - setVariable:
+                            name: hello
+                            simple:
+                              expression: Camel
+                        - to:
+                            uri: mock:before
+                        - to:
+                            uri: direct:foo
+                            variableSend: hello
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:receive
+                      steps:
+                        - to:
+                            uri: direct:foo
+                            variableReceive: bye
+                        - to:
+                            uri: mock:after
+                        - setBody:
+                            simple:
+                              expression: "${variable:bye}"
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:sendAndReceive
+                      steps:
+                        - setVariable:
+                            name: hello
+                            simple:
+                              expression: Camel
+                        - to:
+                            uri: mock:before
+                        - to:
+                            uri: direct:foo
+                            variableReceive: bye
+                            variableSend: hello
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:foo
+                      steps:
+                        - transform:
+                            simple:
+                              expression: "Bye ${body}"
+            '''
+
+            withMock('mock:before') {
+                expectedBodiesReceived 'World'
+                expectedVariableReceived("hello", "Camel")
+            }
+            withMock('mock:result') {
+                expectedBodiesReceived 'Bye Camel'
+                expectedVariableReceived("hello", "Camel")
+            }
+
+        when:
+            context.start()
+
+            withTemplate {
+                to('direct:send').withBody('World').send()
+            }
+
+        then:
+            MockEndpoint.assertIsSatisfied(context)
+    }
+
+
+    def "toVariable receive"() {
+        setup:
+        loadRoutes '''
+                - route:
+                    from:
+                      uri: direct:send
+                      steps:
+                        - setVariable:
+                            name: hello
+                            simple:
+                              expression: Camel
+                        - to:
+                            uri: mock:before
+                        - to:
+                            uri: direct:foo
+                            variableSend: hello
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:receive
+                      steps:
+                        - to:
+                            uri: direct:foo
+                            variableReceive: bye
+                        - to:
+                            uri: mock:after
+                        - setBody:
+                            simple:
+                              expression: "${variable:bye}"
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:sendAndReceive
+                      steps:
+                        - setVariable:
+                            name: hello
+                            simple:
+                              expression: Camel
+                        - to:
+                            uri: mock:before
+                        - to:
+                            uri: direct:foo
+                            variableReceive: bye
+                            variableSend: hello
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:foo
+                      steps:
+                        - transform:
+                            simple:
+                              expression: "Bye ${body}"
+            '''
+
+        withMock('mock:after') {
+            expectedBodiesReceived 'World'
+            expectedVariableReceived("bye", "Bye World")
+        }
+        withMock('mock:result') {
+            expectedBodiesReceived 'Bye World'
+            expectedVariableReceived("bye", "Bye World")
+        }
+
+        when:
+        context.start()
+
+        withTemplate {
+            to('direct:receive').withBody('World').send()
+        }
+
+        then:
+        MockEndpoint.assertIsSatisfied(context)
+    }
+
+    def "toVariable sendAndReceive"() {
+        setup:
+        loadRoutes '''
+                - route:
+                    from:
+                      uri: direct:send
+                      steps:
+                        - setVariable:
+                            name: hello
+                            simple:
+                              expression: Camel
+                        - to:
+                            uri: mock:before
+                        - to:
+                            uri: direct:foo
+                            variableSend: hello
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:receive
+                      steps:
+                        - to:
+                            uri: direct:foo
+                            variableReceive: bye
+                        - to:
+                            uri: mock:after
+                        - setBody:
+                            simple:
+                              expression: "${variable:bye}"
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:sendAndReceive
+                      steps:
+                        - setVariable:
+                            name: hello
+                            simple:
+                              expression: Camel
+                        - to:
+                            uri: mock:before
+                        - to:
+                            uri: direct:foo
+                            variableReceive: bye
+                            variableSend: hello
+                        - to:
+                            uri: mock:result
+                - route:
+                    from:
+                      uri: direct:foo
+                      steps:
+                        - transform:
+                            simple:
+                              expression: "Bye ${body}"
+            '''
+
+        withMock('mock:before') {
+            expectedBodiesReceived 'World'
+            expectedVariableReceived("hello", "Camel")
+        }
+        withMock('mock:result') {
+            expectedBodiesReceived 'World'
+            expectedVariableReceived("bye", "Bye Camel")
+        }
+
+        when:
+        context.start()
+
+        withTemplate {
+            to('direct:sendAndReceive').withBody('World').send()
+        }
+
+        then:
+        MockEndpoint.assertIsSatisfied(context)
+    }
+}