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/31 09:07:16 UTC

(camel) branch var-headers created (now afb444407a5)

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

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


      at afb444407a5 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

This branch includes the following new commits:

     new 9172b879c8e CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 0cba30cf1ab CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 7fe558b0de3 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 0731a282086 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 8fff1ac7ff7 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new a7dca4d3ba2 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 6217381bbd2 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 815ddbdd753 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new a3b2064dd40 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 591b9691476 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new 6108ed401c1 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new a6ea966df85 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new ae180c6195b CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new c8be513a82a CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new ad77b1719fa CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
     new afb444407a5 CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

The 16 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(camel) 16/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit afb444407a572236e88ce52572bd3bf82e9bb919
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jan 31 10:03:15 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../modules/languages/pages/simple-language.adoc   |  2 ++
 .../simple/ast/SimpleFunctionExpression.java       | 12 ++++++++++
 .../apache/camel/language/simple/SimpleTest.java   |  9 ++++++++
 .../camel/support/builder/ExpressionBuilder.java   | 26 ++++++++++++++++++++++
 4 files changed, 49 insertions(+)

diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index 2036d642303..acb01a71d40 100644
--- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -292,6 +292,8 @@ for example to extract data from the message body (in XML format). This requires
 for example to extract data from the message body (in XML format). This requires having camel-xpath JAR on the classpath.
 For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
 
+|pretty(exp) | String | Converts the inlined expression to a String, and attempts to pretty print if JSon or XML, otherwise the expression is returned as the String value.
+
 |=======================================================================
 
 == OGNL expression support
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index 5572e108b0a..a9276250d2e 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -175,6 +175,18 @@ public class SimpleFunctionExpression extends LiteralExpression {
             return SimpleExpressionBuilder.exchangeOgnlExpression(remainder);
         }
 
+        // pretty
+        remainder = ifStartsWithReturnRemainder("pretty(", function);
+        if (remainder != null) {
+            String exp = StringHelper.beforeLast(remainder, ")");
+            if (exp == null) {
+                throw new SimpleParserException("Valid syntax: ${pretty(exp)} was: " + function, token.getIndex());
+            }
+            exp = StringHelper.removeQuotes(exp);
+            Expression inlined = camelContext.resolveLanguage("simple").createExpression(exp);
+            return ExpressionBuilder.prettyExpression(inlined);
+        }
+
         // file: prefix
         remainder = ifStartsWithReturnRemainder("file:", function);
         if (remainder != null) {
diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index 7d3dd1f7bb3..fdcf759ffd7 100644
--- a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -2328,6 +2328,15 @@ public class SimpleTest extends LanguageTestSupport {
         assertThrows(IllegalArgumentException.class, () -> evaluateExpression("${empty(unknownType)}", null));
     }
 
+    @Test
+    public void testPretty() {
+        assertExpression(exchange, "${pretty('Hello')}", "Hello");
+        assertExpression(exchange, "${pretty(${body})}", "<hello id=\"m123\">\n</hello>");
+
+        exchange.getMessage().setBody("{\"name\": \"Jack\", \"id\": 123}");
+        assertExpression(exchange, "${pretty(${body})}", "{\n\t\"name\": \"Jack\",\n\t\"id\": 123\n}\n");
+    }
+
     private void assertExpressionCreateNewEmpty(
             String type, Class<?> expectedClass, java.util.function.Predicate<Object> isEmptyAssertion) {
         Object value = evaluateExpression("${empty(%s)}".formatted(type), null);
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index bc9775de5e5..0f3bb477721 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -2374,6 +2374,32 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns the expression as pretty formatted string
+     */
+    public static Expression prettyExpression(final Expression expression) {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                String body = expression.evaluate(exchange, String.class);
+                if (body == null) {
+                    return null;
+                } else if (body.startsWith("{") && body.endsWith("}") || body.startsWith("[") && body.endsWith("]")) {
+                    return Jsoner.prettyPrint(body); //json
+                } else if (body.startsWith("<") && body.endsWith(">")) {
+                    return ExpressionBuilder.prettyXml(body); //xml
+                }
+
+                return body;
+            }
+
+            @Override
+            public String toString() {
+                return "pretty(" + expression + ")";
+            }
+        };
+    }
+
     /**
      * Returns the expression for the message body as pretty formatted string
      */


(camel) 08/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 815ddbdd753df6140dcad5f73c70e0a11bd317e0
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 14:09:41 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../src/test/java/org/apache/camel/language/VariableTest.java         | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
index 41eeb16aa1b..6e28f0116f1 100644
--- a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
@@ -16,12 +16,12 @@
  */
 package org.apache.camel.language;
 
+import java.util.Map;
+
 import org.apache.camel.LanguageTestSupport;
 import org.apache.camel.language.variable.VariableLanguage;
 import org.junit.jupiter.api.Test;
 
-import java.util.Map;
-
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;


(camel) 04/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0731a28208698444e717edf72414fbaa5faee63a
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 10:59:38 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../java/org/apache/camel/processor/Enricher.java  | 19 ++++-
 .../org/apache/camel/processor/PollEnricher.java   | 17 +++-
 .../camel/processor/EnrichVariableHeadersTest.java | 94 ++++++++++++++++++++++
 .../processor/PollEnrichVariableHeadersTest.java   | 62 ++++++++++++++
 .../org/apache/camel/support/ExchangeHelper.java   |  6 +-
 5 files changed, 187 insertions(+), 11 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 bb8350971ec..9d641b01a05 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
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.AggregationStrategy;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
@@ -26,6 +28,7 @@ import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
 import org.apache.camel.Expression;
 import org.apache.camel.spi.EndpointUtilizationStatistics;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.ProcessorExchangeFactory;
 import org.apache.camel.spi.RouteIdAware;
@@ -63,6 +66,7 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
     private boolean ignoreInvalidEndpoint;
     private boolean allowOptimisedComponents = true;
     private boolean autoStartupComponents = true;
+    private HeadersMapFactory headersMapFactory;
     private ProcessorExchangeFactory processorExchangeFactory;
     private SendDynamicProcessor sendDynamicProcessor;
 
@@ -188,9 +192,12 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object body = null;
+        Map<String, Object> headers = null;
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
+                // do a defensive copy of the headers
+                headers = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -198,6 +205,7 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
             }
         }
         final Object originalBody = body;
+        final Map<String, Object> originalHeaders = headers;
 
         return sendDynamicProcessor.process(resourceExchange, new AsyncCallback() {
             @Override
@@ -216,9 +224,9 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
                         if (aggregatedExchange != null) {
                             if (variableReceive != null) {
                                 // result should be stored in variable instead of message body
-                                Object value = aggregatedExchange.getMessage().getBody();
-                                ExchangeHelper.setVariable(exchange, variableReceive, value);
-                                aggregatedExchange.getMessage().setBody(originalBody);
+                                ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                                exchange.getMessage().setBody(originalBody);
+                                exchange.getMessage().setHeaders(originalHeaders);
                             }
                             // copy aggregation result onto original exchange (preserving pattern)
                             copyResultsWithoutCorrelationId(exchange, aggregatedExchange);
@@ -302,6 +310,11 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
         ServiceHelper.buildService(processorExchangeFactory, sendDynamicProcessor);
     }
 
+    @Override
+    protected void doInit() throws Exception {
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
+    }
+
     @Override
     protected void doStart() throws Exception {
         ServiceHelper.startService(processorExchangeFactory, aggregationStrategy, sendDynamicProcessor);
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 21759c73f24..4005d83834c 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
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.AggregationStrategy;
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
@@ -32,6 +34,7 @@ import org.apache.camel.PollingConsumer;
 import org.apache.camel.spi.ConsumerCache;
 import org.apache.camel.spi.EndpointUtilizationStatistics;
 import org.apache.camel.spi.ExceptionHandler;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.NormalizedEndpointUri;
 import org.apache.camel.spi.RouteIdAware;
@@ -64,7 +67,8 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
 
     private CamelContext camelContext;
     private ConsumerCache consumerCache;
-    protected volatile String scheme;
+    private HeadersMapFactory headersMapFactory;
+    private volatile String scheme;
     private String id;
     private String routeId;
     private String variableReceive;
@@ -320,9 +324,12 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object originalBody = null;
+        Map<String, Object> originalHeaders = null;
         if (variableReceive != null) {
             try {
                 originalBody = exchange.getMessage().getBody();
+                // do a defensive copy of the headers
+                originalHeaders = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -345,9 +352,9 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
                 if (aggregatedExchange != null) {
                     if (variableReceive != null) {
                         // result should be stored in variable instead of message body
-                        Object value = aggregatedExchange.getMessage().getBody();
-                        ExchangeHelper.setVariable(exchange, variableReceive, value);
-                        aggregatedExchange.getMessage().setBody(originalBody);
+                        ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                        exchange.getMessage().setBody(originalBody);
+                        exchange.getMessage().setHeaders(originalHeaders);
                     }
                     // copy aggregation result onto original exchange (preserving pattern)
                     copyResultsPreservePattern(exchange, aggregatedExchange);
@@ -485,6 +492,8 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
             scheme = ExchangeHelper.resolveScheme(u);
         }
 
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
+
         ServiceHelper.initService(consumerCache, aggregationStrategy);
     }
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableHeadersTest.java
new file mode 100644
index 00000000000..229e5c86d4e
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/EnrichVariableHeadersTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class EnrichVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    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");
+        getMockEndpoint("mock:result").message(0).header("echo").isEqualTo("CamelCamel");
+
+        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:after").message(0).header("echo").isNull();
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        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");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:sendAndReceive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:send")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .enrich().constant("direct:foo").variableSend("hello")
+                        .to("mock:result");
+
+                from("direct:receive")
+                        .enrich().constant("direct:foo").variableReceive("bye")
+                        .to("mock:after")
+                        .setBody(simple("${variable:bye}"))
+                        .to("mock:result");
+
+                from("direct:sendAndReceive")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .enrich().constant("direct:foo").variableSend("hello").variableReceive("bye")
+                        .to("mock:result");
+
+                from("direct:foo")
+                        .setHeader("echo", simple("${body}${body}"))
+                        .transform().simple("Bye ${body}");
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
new file mode 100644
index 00000000000..852e3b0cd13
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.processor;
+
+import java.util.Map;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class PollEnrichVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    public void testReceive() throws Exception {
+        template.sendBodyAndHeader("seda:foo", "Bye World", "echo", "CamelCamel");
+
+        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");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+        getMockEndpoint("mock:result").whenAnyExchangeReceived(e -> {
+            Map m = e.getVariable("bye.headers", Map.class);
+            Assertions.assertNotNull(m);
+            Assertions.assertEquals(1, m.size());
+            Assertions.assertEquals("CamelCamel", m.get("echo"));
+        });
+
+        template.sendBody("direct:receive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:receive")
+                        .pollEnrich().constant("seda:foo").timeout(1000).variableReceive("bye")
+                        .to("mock:after")
+                        .setBody(simple("${variable:bye}"))
+                        .to("mock:result");
+            }
+        };
+    }
+}
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 85c34ecf86a..02df6ccce7b 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
@@ -1094,8 +1094,7 @@ public final class ExchangeHelper {
                 name = StringHelper.after(name, ":");
                 repo.setVariable(name, value);
             } else {
-                exchange.setException(
-                        new IllegalArgumentException("VariableRepository with id: " + id + " does not exist"));
+                throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
             }
         } else {
             exchange.setVariable(name, value);
@@ -1110,8 +1109,7 @@ public final class ExchangeHelper {
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
             repo = factory.getVariableRepository(id);
             if (repo == null) {
-                exchange.setException(
-                        new IllegalArgumentException("VariableRepository with id: " + id + " does not exist"));
+                throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
             }
             name = StringHelper.after(name, ":");
         }


(camel) 13/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ae180c6195ba97739d0248b8dc425b1f25ebf817
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jan 31 06:20:30 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 docs/user-manual/modules/ROOT/pages/variables.adoc | 20 ++++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc b/docs/user-manual/modules/ROOT/pages/variables.adoc
index c277a9b37ec..e392cd26fcc 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -190,7 +190,7 @@ or with the xref:components:eips:claimCheck-eip.adoc[Claim Check] EIP.
 
 === Important concept when using variables and EIPs
 
-It is **important** to understand that the variables focuses the use of the message **body** only, and makes using **headers** optional.
+It is **important** to understand that the variables focuses the use of the message **body** only.
 This is on purpose to keep it simpler and primary work with the message body as the user data.
 
 The following table summarises what the EIP supports with variables:
@@ -210,7 +210,23 @@ The following table summarises what the EIP supports with variables:
 The EIPs listed above have support for using variables when sending and receiving data. This is done by using the `variableSend` and `variableReceive` options
 to specify the name of the variable.
 
-When the EIP is using variables, then the `Message` on the `Exchange` is not in use, but the body and headers will be from the variable.
+The EIPs works in two modes where *variableSend* and *variableReceive* is a little bit different, so pay attention to the following table:
+
+|===
+| *VariableSend*       | *VariableReceive*
+| *Headers:* Message   | *Headers:* Variable
+| *Body:* Variable     | *Body:* Variable
+|===
+
+The *VariableSend* is intended for sending as regular Camel where the headers are from the current `Message` and the body is
+the variable. In other words it's only the message body that is taken from the variable instead of the current `Message` body.
+
+The *VariableReceive* works in a different mode. The idea is that all the received data is stored as variables. This means the current `Message`
+is not changed at all. The body is stored in the variable, and the received headers (transport headers etc.) are stored as read-only
+headers as variables as well. The names of the variable is `header:variableName.headerName`. For example if the variable is `myVar` and the header is `Content-Type`
+then the header is stored as `header:myVar.Content-Type`.
+
+When the EIP is using *VariableReceive*, then the `Message` on the `Exchange` is not in use, but the body and headers will be from the variable.
 For example given the following `Message` containing:
 
 [source,properties]


(camel) 01/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 9172b879c8e07ce9b958da883a65b9ad33e23bdd
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 09:32:30 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../camel/spring/processor/FromVariableTest.xml    |  2 ++
 .../org/apache/camel/reifier/RouteReifier.java     |  8 ++++++--
 .../apache/camel/processor/FromVariableTest.java   | 24 ++++++++++++++++++++++
 .../apache/camel/dsl/yaml/FromVariableTest.groovy  | 15 +++++++++++++-
 4 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml
index 1f578ec457d..595d382242e 100644
--- a/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml
+++ b/components/camel-spring-xml/src/test/resources/org/apache/camel/spring/processor/FromVariableTest.xml
@@ -29,6 +29,8 @@
     <jmxAgent id="jmx" disabled="true"/>
     <route>
       <from uri="direct:start" variableReceive="myKey"/>
+      <setHeader name="foo"><constant>456</constant></setHeader>
+      <setHeader name="bar"><constant>Murphy</constant></setHeader>
       <transform><simple>Bye ${body}</simple></transform>
       <to uri="mock:foo"/>
       <setBody>
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 d53870207a7..3bdd8679223 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
@@ -45,6 +45,7 @@ import org.apache.camel.reifier.rest.RestBindingReifier;
 import org.apache.camel.spi.CamelInternalProcessorAdvice;
 import org.apache.camel.spi.Contract;
 import org.apache.camel.spi.ErrorHandlerAware;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.InternalProcessor;
 import org.apache.camel.spi.LifecycleStrategy;
 import org.apache.camel.spi.ManagementInterceptStrategy;
@@ -331,7 +332,7 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
 
         // wrap with variable
         if (variable != null) {
-            internal.addAdvice(new VariableAdvice(variable));
+            internal.addAdvice(new VariableAdvice(camelContext, variable));
         }
 
         // and create the route that wraps all of this
@@ -426,9 +427,11 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
      */
     private static class VariableAdvice implements CamelInternalProcessorAdvice<Object> {
 
+        private final HeadersMapFactory factory;
         private final String name;
 
-        public VariableAdvice(String name) {
+        public VariableAdvice(CamelContext camelContext, String name) {
+            this.factory = camelContext.getCamelContextExtension().getHeadersMapFactory();
             this.name = name;
         }
 
@@ -436,6 +439,7 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
         public Object before(Exchange exchange) throws Exception {
             Object body = exchange.getMessage().getBody();
             ExchangeHelper.setVariable(exchange, name, body);
+            ExchangeHelper.setVariable(exchange, name + ".headers", factory.newMap(exchange.getMessage().getHeaders()));
             return null;
         }
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
index 3dfc5800ee8..cc1364e31e0 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
@@ -18,8 +18,11 @@ package org.apache.camel.processor;
 
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
+import java.util.Map;
+
 public class FromVariableTest extends ContextTestSupport {
 
     @Test
@@ -32,12 +35,33 @@ public class FromVariableTest extends ContextTestSupport {
         assertMockEndpointsSatisfied();
     }
 
+    @Test
+    public void testOriginalHeaders() throws Exception {
+        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:foo").expectedHeaderReceived("foo", 456);
+        getMockEndpoint("mock:foo").whenAnyExchangeReceived(e -> {
+            Map m = e.getVariable("myKey.headers", Map.class);
+            Assertions.assertNotNull(m);
+            Assertions.assertEquals(1, m.size());
+            Assertions.assertEquals(123, m.get("foo"));
+        });
+
+        getMockEndpoint("mock:result").expectedBodiesReceived("World");
+        getMockEndpoint("mock:result").expectedHeaderReceived("foo", 456);
+
+        template.sendBodyAndHeader("direct:start", "World", "foo", 123);
+
+        assertMockEndpointsSatisfied();
+    }
+
     @Override
     protected RouteBuilder createRouteBuilder() throws Exception {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
                 fromV("direct:start", "myKey")
+                        .setHeader("foo", constant(456))
+                        .setHeader("bar", constant("Murphy"))
                         .transform().simple("Bye ${body}")
                         .to("mock:foo")
                         .setBody(simple("${variable:myKey}"))
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
index 7316a4f874a..5984b103e00 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.yaml
 
 import org.apache.camel.component.mock.MockEndpoint
 import org.apache.camel.dsl.yaml.support.YamlTestSupport
+import org.junit.jupiter.api.Assertions
 
 class FromVariableTest extends YamlTestSupport {
 
@@ -28,6 +29,12 @@ class FromVariableTest extends YamlTestSupport {
                     uri: "direct:start"
                     variableReceive: "myKey"
                     steps:
+                      - setHeader:
+                          name: foo
+                          constant: "456"
+                      - setHeader:
+                          name: bar
+                          constant: "Murphy"
                       - transform:
                           simple: "Bye ${body}"
                       - to: "mock:foo"
@@ -38,6 +45,12 @@ class FromVariableTest extends YamlTestSupport {
 
             withMock('mock:foo') {
                 expectedBodiesReceived 'Bye World'
+                whenAnyExchangeReceived { e -> {
+                    Map m = e.getVariable("myKey.headers", Map.class)
+                    Assertions.assertNotNull(m)
+                    Assertions.assertEquals(1, m.size())
+                    Assertions.assertEquals(123, m.get("foo"))
+                }}
             }
             withMock('mock:result') {
                 expectedBodiesReceived 'World'
@@ -47,7 +60,7 @@ class FromVariableTest extends YamlTestSupport {
             context.start()
 
             withTemplate {
-                to('direct:start').withBody('World').send()
+                to('direct:start').withBody('World').withHeader("foo", 123).send()
             }
 
         then:


(camel) 10/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 591b96914769ae82cf8c9dc09897bc395bbd3597
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Jan 30 10:22:07 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../apache/camel/spi/StreamCachingStrategy.java    |  8 +++
 .../impl/engine/DefaultStreamCachingStrategy.java  | 18 ++++-
 .../camel/language/tokenizer/TokenizeLanguage.java |  3 +-
 ...sitory.java => AbstractVariableRepository.java} | 83 +++++++++++++---------
 .../camel/support/ExchangeVariableRepository.java  | 74 ++-----------------
 .../camel/support/GlobalVariableRepository.java    | 60 +---------------
 .../camel/support/builder/ExpressionBuilder.java   |  8 +--
 7 files changed, 85 insertions(+), 169 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java b/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java
index 68de83dd718..267949c9ba6 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/StreamCachingStrategy.java
@@ -282,4 +282,12 @@ public interface StreamCachingStrategy extends StaticService {
      */
     StreamCache cache(Message message);
 
+    /**
+     * Caches the value aas a {@link StreamCache}.
+     *
+     * @param  value the value
+     * @return       the value cached as a {@link StreamCache}, or <tt>null</tt> if not possible or no need to cache
+     */
+    StreamCache cache(Object value);
+
 }
diff --git a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java
index 4c0919f7b7e..92df0417bd8 100644
--- a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java
+++ b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultStreamCachingStrategy.java
@@ -249,21 +249,33 @@ public class DefaultStreamCachingStrategy extends ServiceSupport implements Came
 
     @Override
     public StreamCache cache(Exchange exchange) {
-        return cache(exchange.getMessage());
+        return doCache(exchange.getMessage().getBody(), exchange);
     }
 
     @Override
     public StreamCache cache(Message message) {
+        return doCache(message.getBody(), message.getExchange());
+    }
+
+    @Override
+    public StreamCache cache(Object body) {
+        return doCache(body, null);
+    }
+
+    private StreamCache doCache(Object body, Exchange exchange) {
         StreamCache cache = null;
         // try convert to stream cache
-        Object body = message.getBody();
         if (body != null) {
             boolean allowed = allowClasses == null && denyClasses == null;
             if (!allowed) {
                 allowed = checkAllowDenyList(body);
             }
             if (allowed) {
-                cache = camelContext.getTypeConverter().convertTo(StreamCache.class, message.getExchange(), body);
+                if (exchange != null) {
+                    cache = camelContext.getTypeConverter().convertTo(StreamCache.class, exchange, body);
+                } else {
+                    cache = camelContext.getTypeConverter().convertTo(StreamCache.class, body);
+                }
             }
         }
         if (cache != null) {
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
index 62b63391ba1..3662e1b0284 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
@@ -133,7 +133,8 @@ public class TokenizeLanguage extends SingleInputLanguageSupport implements Prop
 
         if (answer == null) {
             // use the regular tokenizer
-            final Expression exp = ExpressionBuilder.singleInputExpression(getVariableName(), getHeaderName(), getPropertyName());
+            final Expression exp
+                    = ExpressionBuilder.singleInputExpression(getVariableName(), getHeaderName(), getPropertyName());
             if (regex) {
                 answer = ExpressionBuilder.regexTokenizeExpression(exp, token);
             } else {
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractVariableRepository.java
similarity index 58%
copy from core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
copy to core/camel-support/src/main/java/org/apache/camel/support/AbstractVariableRepository.java
index 2101982bf4a..ba6655b0541 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractVariableRepository.java
@@ -22,49 +22,35 @@ import java.util.stream.Stream;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
-import org.apache.camel.Exchange;
-import org.apache.camel.NonManagedService;
 import org.apache.camel.StreamCache;
-import org.apache.camel.converter.stream.CachedOutputStream;
+import org.apache.camel.StreamCacheException;
 import org.apache.camel.spi.BrowsableVariableRepository;
-import org.apache.camel.spi.VariableRepository;
+import org.apache.camel.spi.StreamCachingStrategy;
 import org.apache.camel.support.service.ServiceSupport;
-import org.apache.camel.util.CaseInsensitiveMap;
-import org.apache.camel.util.StringHelper;
 
 /**
- * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables.
+ * Base class for {@link org.apache.camel.spi.VariableRepository} implementations that store variables in memory.
  */
-class ExchangeVariableRepository extends ServiceSupport implements BrowsableVariableRepository, NonManagedService {
+public abstract class AbstractVariableRepository extends ServiceSupport
+        implements BrowsableVariableRepository, CamelContextAware {
 
     private final Map<String, Object> variables = new ConcurrentHashMap<>(8);
-    private final CamelContext camelContext;
+    private CamelContext camelContext;
+    private StreamCachingStrategy strategy;
 
-    public ExchangeVariableRepository(CamelContext camelContext) {
-        this.camelContext = camelContext;
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
     }
 
     @Override
-    public String getId() {
-        return "exchange";
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
     }
 
     @Override
     public Object getVariable(String name) {
         Object answer = variables.get(name);
-        if (answer == null && name.endsWith(".headers")) {
-            String prefix = name.substring(0, name.length() - 1) + "."; // xxx.headers -> xxx.header.
-            // we want all headers for a given variable
-            Map<String, Object> map = new CaseInsensitiveMap();
-            for (Map.Entry<String, Object> entry : variables.entrySet()) {
-                String key = entry.getKey();
-                if (key.startsWith(prefix)) {
-                    key = StringHelper.after(key, prefix);
-                    map.put(key, entry.getValue());
-                }
-            }
-            return map;
-        }
         if (answer instanceof StreamCache sc) {
             // reset so the cache is ready to be used as a variable
             sc.reset();
@@ -74,15 +60,10 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
 
     @Override
     public void setVariable(String name, Object value) {
-        // special for some values that are CachedOutputStream which we want to be re-readable and therefore
-        // convert this to StreamCache
-        // TODO: Do something like StreamCachingHelper
-        // TODO: support base class that has stream caching stuff for set/getVariable
-        if (camelContext.isStreamCaching())
-            return StreamCachingHelper.convertToStreamCache(strategy, exchange, exchange.getIn());
-            Object cache = camelContext.getTypeConverter().tryConvertTo(StreamCache.class, value);
-            if (cache != null) {
-                value = cache;
+        if (value != null && strategy != null) {
+            StreamCache sc = convertToStreamCache(value);
+            if (sc != null) {
+                value = sc;
             }
         }
         if (value != null) {
@@ -125,4 +106,36 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
         }
         return variables.remove(name);
     }
+
+    @Override
+    protected void doInit() throws Exception {
+        super.doInit();
+
+        if (camelContext != null && camelContext.isStreamCaching()) {
+            strategy = camelContext.getStreamCachingStrategy();
+        }
+    }
+
+    protected StreamCache convertToStreamCache(Object body) {
+        // check if body is already cached
+        if (body == null) {
+            return null;
+        } else if (body instanceof StreamCache) {
+            StreamCache sc = (StreamCache) body;
+            // reset so the cache is ready to be used before processing
+            sc.reset();
+            return sc;
+        }
+        return tryStreamCache(body);
+    }
+
+    protected StreamCache tryStreamCache(Object body) {
+        try {
+            // cache the body and if we could do that replace it as the new body
+            return strategy.cache(body);
+        } catch (Exception e) {
+            throw new StreamCacheException(body, e);
+        }
+    }
+
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
index 2101982bf4a..1d75855542f 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
@@ -17,31 +17,24 @@
 package org.apache.camel.support;
 
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.stream.Stream;
 
 import org.apache.camel.CamelContext;
-import org.apache.camel.CamelContextAware;
 import org.apache.camel.Exchange;
-import org.apache.camel.NonManagedService;
 import org.apache.camel.StreamCache;
-import org.apache.camel.converter.stream.CachedOutputStream;
-import org.apache.camel.spi.BrowsableVariableRepository;
 import org.apache.camel.spi.VariableRepository;
-import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.CaseInsensitiveMap;
 import org.apache.camel.util.StringHelper;
 
 /**
  * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables.
  */
-class ExchangeVariableRepository extends ServiceSupport implements BrowsableVariableRepository, NonManagedService {
-
-    private final Map<String, Object> variables = new ConcurrentHashMap<>(8);
-    private final CamelContext camelContext;
+final class ExchangeVariableRepository extends AbstractVariableRepository {
 
     public ExchangeVariableRepository(CamelContext camelContext) {
-        this.camelContext = camelContext;
+        setCamelContext(camelContext);
+        // ensure its started
+        ServiceHelper.startService(this);
     }
 
     @Override
@@ -51,12 +44,12 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
 
     @Override
     public Object getVariable(String name) {
-        Object answer = variables.get(name);
+        Object answer = super.getVariable(name);
         if (answer == null && name.endsWith(".headers")) {
             String prefix = name.substring(0, name.length() - 1) + "."; // xxx.headers -> xxx.header.
             // we want all headers for a given variable
             Map<String, Object> map = new CaseInsensitiveMap();
-            for (Map.Entry<String, Object> entry : variables.entrySet()) {
+            for (Map.Entry<String, Object> entry : getVariables().entrySet()) {
                 String key = entry.getKey();
                 if (key.startsWith(prefix)) {
                     key = StringHelper.after(key, prefix);
@@ -72,57 +65,4 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
         return answer;
     }
 
-    @Override
-    public void setVariable(String name, Object value) {
-        // special for some values that are CachedOutputStream which we want to be re-readable and therefore
-        // convert this to StreamCache
-        // TODO: Do something like StreamCachingHelper
-        // TODO: support base class that has stream caching stuff for set/getVariable
-        if (camelContext.isStreamCaching())
-            return StreamCachingHelper.convertToStreamCache(strategy, exchange, exchange.getIn());
-            Object cache = camelContext.getTypeConverter().tryConvertTo(StreamCache.class, value);
-            if (cache != null) {
-                value = cache;
-            }
-        }
-        if (value != null) {
-            // avoid the NullPointException
-            variables.put(name, value);
-        } else {
-            // if the value is null, we just remove the key from the map
-            variables.remove(name);
-        }
-    }
-
-    public boolean hasVariables() {
-        return !variables.isEmpty();
-    }
-
-    public int size() {
-        return variables.size();
-    }
-
-    public Stream<String> names() {
-        return variables.keySet().stream();
-    }
-
-    public Map<String, Object> getVariables() {
-        return variables;
-    }
-
-    public void setVariables(Map<String, Object> map) {
-        variables.putAll(map);
-    }
-
-    public void clear() {
-        variables.clear();
-    }
-
-    @Override
-    public Object removeVariable(String name) {
-        if (!hasVariables()) {
-            return null;
-        }
-        return variables.remove(name);
-    }
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java
index 09f1e7d7270..cf7abcd1fd8 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/GlobalVariableRepository.java
@@ -17,75 +17,17 @@
 package org.apache.camel.support;
 
 import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.stream.Stream;
 
-import org.apache.camel.StreamCache;
-import org.apache.camel.spi.BrowsableVariableRepository;
 import org.apache.camel.spi.VariableRepository;
-import org.apache.camel.support.service.ServiceSupport;
 
 /**
  * Global {@link VariableRepository} which stores variables in-memory in a {@link Map}.
  */
-public final class GlobalVariableRepository extends ServiceSupport implements BrowsableVariableRepository {
-
-    private final ConcurrentMap<String, Object> variables = new ConcurrentHashMap<>();
+public final class GlobalVariableRepository extends AbstractVariableRepository {
 
     @Override
     public String getId() {
         return "global";
     }
 
-    @Override
-    public Object getVariable(String name) {
-        Object answer = variables.get(name);
-        if (answer instanceof StreamCache sc) {
-            // reset so the cache is ready to be used as a variable
-            sc.reset();
-        }
-        return answer;
-    }
-
-    @Override
-    public void setVariable(String name, Object value) {
-        if (value != null) {
-            // avoid the NullPointException
-            variables.put(name, value);
-        } else {
-            // if the value is null, we just remove the key from the map
-            variables.remove(name);
-        }
-    }
-
-    @Override
-    public Object removeVariable(String name) {
-        return variables.remove(name);
-    }
-
-    @Override
-    public boolean hasVariables() {
-        return !variables.isEmpty();
-    }
-
-    @Override
-    public int size() {
-        return variables.size();
-    }
-
-    @Override
-    public Stream<String> names() {
-        return variables.keySet().stream();
-    }
-
-    @Override
-    public Map<String, Object> getVariables() {
-        return variables;
-    }
-
-    @Override
-    public void clear() {
-        variables.clear();
-    }
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index 4853d3eafe7..6ea8d094b0b 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -1142,13 +1142,13 @@ public class ExpressionBuilder {
     }
 
     /**
-     * @param variableName  the name of the variable from which the input data must be extracted if not empty.
+     * @param  variableName the name of the variable from which the input data must be extracted if not empty.
      * @param  headerName   the name of the header from which the input data must be extracted if not empty.
      * @param  propertyName the name of the property from which the input data must be extracted if not empty and
      *                      {@code headerName} is empty.
-     * @return              a variable expression if {@code variableName} is not empty,
-     *                      a header expression if {@code headerName} is not empty, otherwise a property expression if
-     *                      {@code propertyName} is not empty or finally a body expression.
+     * @return              a variable expression if {@code variableName} is not empty, a header expression if
+     *                      {@code headerName} is not empty, otherwise a property expression if {@code propertyName} is
+     *                      not empty or finally a body expression.
      */
     public static Expression singleInputExpression(String variableName, String headerName, String propertyName) {
         final Expression exp;


(camel) 09/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a3b2064dd40895aebe43737cf5a39f8e2a30acf4
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Jan 30 06:37:09 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../apache/camel/catalog/languages/hl7terser.json  |  9 ++++----
 .../org/apache/camel/catalog/languages/jq.json     |  9 ++++----
 .../apache/camel/catalog/languages/jsonpath.json   |  9 ++++----
 .../org/apache/camel/catalog/models/hl7terser.json |  9 ++++----
 .../org/apache/camel/catalog/models/jq.json        |  9 ++++----
 .../org/apache/camel/catalog/models/jsonpath.json  |  9 ++++----
 .../apache/camel/catalog/schemas/camel-spring.xsd  |  9 ++++++++
 .../org/apache/camel/component/hl7/hl7terser.json  |  9 ++++----
 .../resources/org/apache/camel/language/jq/jq.json |  9 ++++----
 .../org/apache/camel/language/jq/JqExpression.java | 24 +++++++++++++++++--
 .../org/apache/camel/language/jq/JqLanguage.java   |  2 ++
 .../org/apache/camel/jsonpath/jsonpath.json        |  9 ++++----
 .../apache/camel/jsonpath/JsonPathExpression.java  | 12 ++++++++++
 .../apache/camel/jsonpath/JsonPathLanguage.java    |  8 ++++++-
 .../camel/language/tokenizer/TokenizeLanguage.java |  2 +-
 .../org/apache/camel/model/language/hl7terser.json |  9 ++++----
 .../org/apache/camel/model/language/jq.json        |  9 ++++----
 .../org/apache/camel/model/language/jsonpath.json  |  9 ++++----
 .../SingleInputTypedExpressionDefinition.java      | 27 ++++++++++++++++++++++
 .../language/JsonPathExpressionReifier.java        |  3 ++-
 .../SingleInputTypedExpressionReifier.java         |  3 ++-
 .../org/apache/camel/support/AbstractExchange.java |  6 ++---
 .../camel/support/ExchangeVariableRepository.java  | 19 +++++++++++++++
 .../camel/support/SingleInputLanguageSupport.java  | 18 +++++++++------
 .../support/SingleInputTypedLanguageSupport.java   | 27 ++++++++++++++--------
 .../camel/support/builder/ExpressionBuilder.java   | 11 +++++----
 .../java/org/apache/camel/xml/in/ModelParser.java  |  1 +
 .../java/org/apache/camel/xml/out/ModelWriter.java |  1 +
 .../org/apache/camel/yaml/out/ModelWriter.java     |  1 +
 .../dsl/yaml/deserializers/ModelDeserializers.java | 22 ++++++++++++++++--
 .../generated/resources/schema/camelYamlDsl.json   | 15 ++++++++++++
 31 files changed, 239 insertions(+), 80 deletions(-)

diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json
index 3865481202b..58cd42e820c 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/hl7terser.json
@@ -18,9 +18,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json
index 01859b055b7..2414f6e84ef 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jq.json
@@ -18,9 +18,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json
index 1f60d86297e..ec518053441 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/jsonpath.json
@@ -24,9 +24,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json
index c21ace421f3..175a11f5b72 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/hl7terser.json
@@ -15,9 +15,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json
index c0d966613c3..04fb7ca5648 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jq.json
@@ -15,9 +15,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json
index 18ebc270c0c..7a139068350 100644
--- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json
+++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/models/jsonpath.json
@@ -21,9 +21,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
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 a9f51331f7f..8db2e5a49fb 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
@@ -15242,6 +15242,15 @@ The String representation of the MediaType to output.
   <xs:complexType abstract="true" name="singleInputTypedExpressionDefinition">
     <xs:simpleContent>
       <xs:extension base="tns:typedExpressionDefinition">
+        <xs:attribute name="variableName" type="xs:string">
+          <xs:annotation>
+            <xs:documentation xml:lang="en">
+<![CDATA[
+Name of variable to use as input, instead of the message body It has as higher precedent if other are set.
+]]>
+            </xs:documentation>
+          </xs:annotation>
+        </xs:attribute>
         <xs:attribute name="headerName" type="xs:string">
           <xs:annotation>
             <xs:documentation xml:lang="en">
diff --git a/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json b/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json
index 3865481202b..58cd42e820c 100644
--- a/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json
+++ b/components/camel-hl7/src/generated/resources/org/apache/camel/component/hl7/hl7terser.json
@@ -18,9 +18,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json b/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json
index 01859b055b7..2414f6e84ef 100644
--- a/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json
+++ b/components/camel-jq/src/generated/resources/org/apache/camel/language/jq/jq.json
@@ -18,9 +18,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
index f66c0f8f1ed..a72cc5f90a3 100644
--- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
+++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqExpression.java
@@ -32,9 +32,11 @@ import org.apache.camel.Exchange;
 import org.apache.camel.ExpressionIllegalSyntaxException;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.NoSuchHeaderOrPropertyException;
+import org.apache.camel.NoSuchVariableException;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.spi.ExpressionResultTypeAware;
+import org.apache.camel.support.ExchangeHelper;
 import org.apache.camel.support.ExpressionAdapter;
 import org.apache.camel.support.MessageHelper;
 
@@ -48,6 +50,7 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
     private JsonQuery query;
     private TypeConverter typeConverter;
 
+    private String variableName;
     private String headerName;
     private String propertyName;
 
@@ -117,6 +120,17 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
         this.resultTypeName = resultTypeName;
     }
 
+    public String getVariableName() {
+        return variableName;
+    }
+
+    /**
+     * Name of the variable to use as input instead of the message body.
+     */
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
@@ -206,7 +220,7 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
     private JsonNode getPayload(Exchange exchange) throws Exception {
         JsonNode payload = null;
 
-        if (headerName == null && propertyName == null) {
+        if (variableName == null && headerName == null && propertyName == null) {
             payload = exchange.getMessage().getBody(JsonNode.class);
             if (payload == null) {
                 throw new InvalidPayloadException(exchange, JsonNode.class);
@@ -214,7 +228,13 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
             // if body is stream cached then reset, so we can re-read it again
             MessageHelper.resetStreamCache(exchange.getMessage());
         } else {
-            if (headerName != null) {
+            if (variableName != null) {
+                payload = ExchangeHelper.getVariable(exchange, variableName, JsonNode.class);
+                if (payload == null) {
+                    throw new NoSuchVariableException(exchange, variableName, JsonNode.class);
+                }
+            }
+            if (payload == null && headerName != null) {
                 payload = exchange.getMessage().getHeader(headerName, JsonNode.class);
             }
             if (payload == null && propertyName != null) {
diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java
index c5ef627cf27..91ff60c32d5 100644
--- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java
+++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqLanguage.java
@@ -71,6 +71,7 @@ public class JqLanguage extends SingleInputTypedLanguageSupport implements Stati
     public Expression createExpression(String expression) {
         JqExpression answer = new JqExpression(Scope.newChildScope(rootScope), expression);
         answer.setResultType(getResultType());
+        answer.setVariableName(getVariableName());
         answer.setHeaderName(getHeaderName());
         answer.setPropertyName(getPropertyName());
         answer.init(getCamelContext());
@@ -83,6 +84,7 @@ public class JqLanguage extends SingleInputTypedLanguageSupport implements Stati
         answer.setResultType(property(Class.class, properties, 0, getResultType()));
         answer.setHeaderName(property(String.class, properties, 1, getHeaderName()));
         answer.setPropertyName(property(String.class, properties, 2, getPropertyName()));
+        answer.setVariableName(property(String.class, properties, 3, getVariableName()));
         answer.init(getCamelContext());
         return answer;
     }
diff --git a/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json b/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json
index 1f60d86297e..ec518053441 100644
--- a/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json
+++ b/components/camel-jsonpath/src/generated/resources/org/apache/camel/jsonpath/jsonpath.json
@@ -24,9 +24,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
index 1c8b9cccc29..f0c27d7cd4a 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathExpression.java
@@ -43,6 +43,7 @@ public class JsonPathExpression extends ExpressionAdapter {
     private boolean allowEasyPredicate = true;
     private boolean writeAsString;
     private boolean unpackArray;
+    private String variableName;
     private String headerName;
     private String propertyName;
     private Option[] options;
@@ -129,6 +130,17 @@ public class JsonPathExpression extends ExpressionAdapter {
         this.unpackArray = unpackArray;
     }
 
+    public String getVariableName() {
+        return variableName;
+    }
+
+    /**
+     * Name of variable to use as input, instead of the message body
+     */
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
diff --git a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
index 1dc16711993..b8879cfcbec 100644
--- a/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
+++ b/components/camel-jsonpath/src/main/java/org/apache/camel/jsonpath/JsonPathLanguage.java
@@ -102,9 +102,10 @@ public class JsonPathLanguage extends SingleInputTypedLanguageSupport implements
         answer.setSuppressExceptions(suppressExceptions);
         answer.setAllowSimple(allowSimple);
         answer.setAllowEasyPredicate(allowEasyPredicate);
-        answer.setHeaderName(getHeaderName());
         answer.setWriteAsString(writeAsString);
         answer.setUnpackArray(unpackArray);
+        answer.setVariableName(getVariableName());
+        answer.setHeaderName(getHeaderName());
         answer.setPropertyName(getPropertyName());
         answer.setOptions(options);
         answer.init(getCamelContext());
@@ -137,6 +138,7 @@ public class JsonPathLanguage extends SingleInputTypedLanguageSupport implements
             answer.setOptions(list.toArray(new Option[0]));
         }
         answer.setPropertyName(property(String.class, properties, 8, getPropertyName()));
+        answer.setVariableName(property(String.class, properties, 9, getVariableName()));
         answer.init(getCamelContext());
         return answer;
     }
@@ -178,6 +180,10 @@ public class JsonPathLanguage extends SingleInputTypedLanguageSupport implements
             case "allowEasyPredicate":
                 setAllowEasyPredicate(PropertyConfigurerSupport.property(camelContext, boolean.class, value));
                 return true;
+            case "variablename":
+            case "variableName":
+                setVariableName(PropertyConfigurerSupport.property(camelContext, String.class, value));
+                return true;
             case "headername":
             case "headerName":
                 setHeaderName(PropertyConfigurerSupport.property(camelContext, String.class, value));
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
index 0d9b0da25f8..62b63391ba1 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/tokenizer/TokenizeLanguage.java
@@ -133,7 +133,7 @@ public class TokenizeLanguage extends SingleInputLanguageSupport implements Prop
 
         if (answer == null) {
             // use the regular tokenizer
-            final Expression exp = ExpressionBuilder.singleInputExpression(getHeaderName(), getPropertyName());
+            final Expression exp = ExpressionBuilder.singleInputExpression(getVariableName(), getHeaderName(), getPropertyName());
             if (regex) {
                 answer = ExpressionBuilder.regexTokenizeExpression(exp, token);
             } else {
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json
index c21ace421f3..175a11f5b72 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/hl7terser.json
@@ -15,9 +15,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json
index c0d966613c3..04fb7ca5648 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jq.json
@@ -15,9 +15,10 @@
   "properties": {
     "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" },
     "expression": { "index": 1, "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "The expression value in your chosen language syntax" },
-    "headerName": { "index": 2, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 3, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 4, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 5, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 2, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 3, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 4, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 5, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 6, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json
index 18ebc270c0c..7a139068350 100644
--- a/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json
+++ b/core/camel-core-model/src/generated/resources/org/apache/camel/model/language/jsonpath.json
@@ -21,9 +21,10 @@
     "writeAsString": { "index": 5, "kind": "attribute", "displayName": "Write As String", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to write the output of each row\/element as a JSON String value instead of a Map\/POJO value." },
     "unpackArray": { "index": 6, "kind": "attribute", "displayName": "Unpack Array", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to unpack a single element json-array into an object." },
     "option": { "index": 7, "kind": "attribute", "displayName": "Option", "label": "advanced", "required": false, "type": "enum", "javaType": "java.lang.String", "enum": [ "DEFAULT_PATH_LEAF_TO_NULL", "ALWAYS_RETURN_LIST", "AS_PATH_LIST", "SUPPRESS_EXCEPTIONS", "REQUIRE_PROPERTIES" ], "deprecated": false, "autowired": false, "secret": false, "description": "To configure additional options on JSONPath. Multiple values can be separated by comma." },
-    "headerName": { "index": 8, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
-    "propertyName": { "index": 9, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
-    "resultType": { "index": 10, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
-    "trim": { "index": 11, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
+    "variableName": { "index": 8, "kind": "attribute", "displayName": "Variable Name", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of variable to use as input, instead of the message body It has as higher precedent if other are set." },
+    "headerName": { "index": 9, "kind": "attribute", "displayName": "Header Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of header to use as input, instead of the message body It has as higher precedent than the propertyName if both are set." },
+    "propertyName": { "index": 10, "kind": "attribute", "displayName": "Property Name", "label": "advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set." },
+    "resultType": { "index": 11, "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Sets the class of the result type (type from output)" },
+    "trim": { "index": 12, "kind": "attribute", "displayName": "Trim", "label": "advanced", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }
   }
 }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java
index 8f4e30204da..148d26a6e3b 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/SingleInputTypedExpressionDefinition.java
@@ -27,6 +27,8 @@ import org.apache.camel.spi.Metadata;
  */
 public abstract class SingleInputTypedExpressionDefinition extends TypedExpressionDefinition {
 
+    @XmlAttribute
+    private String variableName;
     @XmlAttribute
     @Metadata(label = "advanced")
     private String headerName;
@@ -47,10 +49,24 @@ public abstract class SingleInputTypedExpressionDefinition extends TypedExpressi
 
     protected SingleInputTypedExpressionDefinition(AbstractBuilder<?, ?> builder) {
         super(builder);
+        this.variableName = builder.variableName;
         this.headerName = builder.headerName;
         this.propertyName = builder.propertyName;
     }
 
+    public String getVariableName() {
+        return variableName;
+    }
+
+    /**
+     * Name of variable to use as input, instead of the message body
+     * </p>
+     * It has as higher precedent if other are set.
+     */
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
+
     public String getHeaderName() {
         return headerName;
     }
@@ -86,9 +102,20 @@ public abstract class SingleInputTypedExpressionDefinition extends TypedExpressi
             T extends AbstractBuilder<T, E>, E extends SingleInputTypedExpressionDefinition>
             extends TypedExpressionDefinition.AbstractBuilder<T, E> {
 
+        private String variableName;
         private String headerName;
         private String propertyName;
 
+        /**
+         * Name of variable to use as input, instead of the message body
+         * </p>
+         * It has as higher precedent if other are set.
+         */
+        public T variableName(String variableName) {
+            this.variableName = variableName;
+            return (T) this;
+        }
+
         /**
          * Name of header to use as input, instead of the message body
          * </p>
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
index 71e3562bb8b..270c7364273 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JsonPathExpressionReifier.java
@@ -28,7 +28,7 @@ public class JsonPathExpressionReifier extends SingleInputTypedExpressionReifier
 
     @Override
     protected Object[] createProperties() {
-        Object[] properties = new Object[9];
+        Object[] properties = new Object[10];
         properties[0] = definition.getResultType();
         properties[1] = parseBoolean(definition.getSuppressExceptions());
         properties[2] = parseBoolean(definition.getAllowSimple());
@@ -38,6 +38,7 @@ public class JsonPathExpressionReifier extends SingleInputTypedExpressionReifier
         properties[6] = parseString(definition.getHeaderName());
         properties[7] = parseString(definition.getOption());
         properties[8] = parseString(definition.getPropertyName());
+        properties[9] = parseString(definition.getVariableName());
         return properties;
     }
 
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java
index 3eab302ec6b..d375320454e 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/SingleInputTypedExpressionReifier.java
@@ -35,10 +35,11 @@ class SingleInputTypedExpressionReifier<T extends SingleInputTypedExpressionDefi
 
     @Override
     protected Object[] createProperties() {
-        Object[] properties = new Object[3];
+        Object[] properties = new Object[4];
         properties[0] = definition.getResultType();
         properties[1] = parseString(definition.getHeaderName());
         properties[2] = parseString(definition.getPropertyName());
+        properties[3] = parseString(definition.getVariableName());
         return properties;
     }
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
index c1aece49c88..4a7f67fb68f 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
@@ -126,7 +126,7 @@ abstract class AbstractExchange implements Exchange {
 
         if (parent.hasVariables()) {
             if (this.variableRepository == null) {
-                this.variableRepository = new ExchangeVariableRepository();
+                this.variableRepository = new ExchangeVariableRepository(getContext());
             }
             this.variableRepository.setVariables(parent.getVariables());
 
@@ -403,7 +403,7 @@ abstract class AbstractExchange implements Exchange {
     @Override
     public void setVariable(String name, Object value) {
         if (variableRepository == null) {
-            variableRepository = new ExchangeVariableRepository();
+            variableRepository = new ExchangeVariableRepository(getContext());
         }
         variableRepository.setVariable(name, value);
     }
@@ -424,7 +424,7 @@ abstract class AbstractExchange implements Exchange {
     public Map<String, Object> getVariables() {
         if (variableRepository == null) {
             // force creating variables
-            variableRepository = new ExchangeVariableRepository();
+            variableRepository = new ExchangeVariableRepository(getContext());
         }
         return variableRepository.getVariables();
     }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
index b913ef26056..2101982bf4a 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
@@ -20,9 +20,12 @@ import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.stream.Stream;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.Exchange;
 import org.apache.camel.NonManagedService;
 import org.apache.camel.StreamCache;
+import org.apache.camel.converter.stream.CachedOutputStream;
 import org.apache.camel.spi.BrowsableVariableRepository;
 import org.apache.camel.spi.VariableRepository;
 import org.apache.camel.support.service.ServiceSupport;
@@ -35,6 +38,11 @@ import org.apache.camel.util.StringHelper;
 class ExchangeVariableRepository extends ServiceSupport implements BrowsableVariableRepository, NonManagedService {
 
     private final Map<String, Object> variables = new ConcurrentHashMap<>(8);
+    private final CamelContext camelContext;
+
+    public ExchangeVariableRepository(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
 
     @Override
     public String getId() {
@@ -66,6 +74,17 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
 
     @Override
     public void setVariable(String name, Object value) {
+        // special for some values that are CachedOutputStream which we want to be re-readable and therefore
+        // convert this to StreamCache
+        // TODO: Do something like StreamCachingHelper
+        // TODO: support base class that has stream caching stuff for set/getVariable
+        if (camelContext.isStreamCaching())
+            return StreamCachingHelper.convertToStreamCache(strategy, exchange, exchange.getIn());
+            Object cache = camelContext.getTypeConverter().tryConvertTo(StreamCache.class, value);
+            if (cache != null) {
+                value = cache;
+            }
+        }
         if (value != null) {
             // avoid the NullPointException
             variables.put(name, value);
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java
index 86703cea8f5..eabffb5a5a7 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputLanguageSupport.java
@@ -23,16 +23,20 @@ import org.apache.camel.spi.Language;
  */
 public abstract class SingleInputLanguageSupport extends LanguageSupport {
 
-    /**
-     * Name of header to use as input, instead of the message body
-     */
+    private String variableName;
     private String headerName;
+    private String propertyName;
+
+    public String getVariableName() {
+        return variableName;
+    }
+
     /**
-     * Name of property to use as input, instead of the message body.
-     * <p>
-     * It has a lower precedent than the name of header if both are set.
+     * Name of variable to use as input, instead of the message body
      */
-    private String propertyName;
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
 
     public String getHeaderName() {
         return headerName;
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java
index 3965287b485..9e1f98a8805 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/SingleInputTypedLanguageSupport.java
@@ -25,16 +25,22 @@ import org.apache.camel.support.builder.ExpressionBuilder;
  */
 public abstract class SingleInputTypedLanguageSupport extends TypedLanguageSupport {
 
-    /**
-     * Name of header to use as input, instead of the message body
-     */
+    private String variableName;
     private String headerName;
+    private String propertyName;
+
+    public String getVariableName() {
+        return variableName;
+    }
+
     /**
-     * Name of property to use as input, instead of the message body.
-     * <p>
-     * It has a lower precedent than the name of header if both are set.
+     * Name of variable to use as input, instead of the message body
+     * </p>
+     * It has as higher precedent if other are set.
      */
-    private String propertyName;
+    public void setVariableName(String variableName) {
+        this.variableName = variableName;
+    }
 
     public String getHeaderName() {
         return headerName;
@@ -63,9 +69,10 @@ public abstract class SingleInputTypedLanguageSupport extends TypedLanguageSuppo
     @Override
     public Expression createExpression(String expression, Object[] properties) {
         Class<?> type = property(Class.class, properties, 0, getResultType());
-        String header = property(String.class, properties, 1, getHeaderName());
-        String property = property(String.class, properties, 2, getPropertyName());
-        Expression source = ExpressionBuilder.singleInputExpression(header, property);
+        String variable = property(String.class, properties, 1, getVariableName());
+        String header = property(String.class, properties, 2, getHeaderName());
+        String property = property(String.class, properties, 3, getPropertyName());
+        Expression source = ExpressionBuilder.singleInputExpression(variable, header, property);
         if (type == null || type == Object.class) {
             return createExpression(source, expression, properties);
         }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index 12271bde384..4853d3eafe7 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -1142,15 +1142,19 @@ public class ExpressionBuilder {
     }
 
     /**
+     * @param variableName  the name of the variable from which the input data must be extracted if not empty.
      * @param  headerName   the name of the header from which the input data must be extracted if not empty.
      * @param  propertyName the name of the property from which the input data must be extracted if not empty and
      *                      {@code headerName} is empty.
-     * @return              a header expression if {@code headerName} is not empty, otherwise a property expression if
+     * @return              a variable expression if {@code variableName} is not empty,
+     *                      a header expression if {@code headerName} is not empty, otherwise a property expression if
      *                      {@code propertyName} is not empty or finally a body expression.
      */
-    public static Expression singleInputExpression(String headerName, String propertyName) {
+    public static Expression singleInputExpression(String variableName, String headerName, String propertyName) {
         final Expression exp;
-        if (ObjectHelper.isNotEmpty(headerName)) {
+        if (ObjectHelper.isNotEmpty(variableName)) {
+            exp = variableExpression(variableName);
+        } else if (ObjectHelper.isNotEmpty(headerName)) {
             exp = headerExpression(headerName);
         } else if (ObjectHelper.isNotEmpty(propertyName)) {
             exp = exchangePropertyExpression(propertyName);
@@ -1163,7 +1167,6 @@ public class ExpressionBuilder {
     /**
      * Returns the expression for the current thread id
      */
-
     public static Expression threadIdExpression() {
         return new ExpressionAdapter() {
             @Override
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
index 60132d4ae6f..4ae07023836 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java
@@ -2895,6 +2895,7 @@ public class ModelParser extends BaseParser {
             switch (key) {
                 case "headerName": def.setHeaderName(val); break;
                 case "propertyName": def.setPropertyName(val); break;
+                case "variableName": def.setVariableName(val); break;
                 default: return typedExpressionDefinitionAttributeHandler().accept(def, key, val);
             }
             return true;
diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
index 6399d61082a..d80ec03e463 100644
--- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
+++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/out/ModelWriter.java
@@ -4161,6 +4161,7 @@ public class ModelWriter extends BaseWriter {
             throws IOException {
         doWriteTypedExpressionDefinitionAttributes(def);
         doWriteAttribute("headerName", def.getHeaderName());
+        doWriteAttribute("variableName", def.getVariableName());
         doWriteAttribute("propertyName", def.getPropertyName());
     }
     protected void doWriteSingleInputTypedExpressionDefinition(
diff --git a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
index fdfcdce5f8b..0a61565954b 100644
--- a/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
+++ b/core/camel-yaml-io/src/generated/java/org/apache/camel/yaml/out/ModelWriter.java
@@ -4161,6 +4161,7 @@ public class ModelWriter extends BaseWriter {
             throws IOException {
         doWriteTypedExpressionDefinitionAttributes(def);
         doWriteAttribute("headerName", def.getHeaderName());
+        doWriteAttribute("variableName", def.getVariableName());
         doWriteAttribute("propertyName", def.getPropertyName());
     }
     protected void doWriteSingleInputTypedExpressionDefinition(
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 2e7c0d1ff1e..5e10764e353 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
@@ -6871,7 +6871,8 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "id", type = "string", description = "Sets the id of this node", displayName = "Id"),
                     @YamlProperty(name = "propertyName", type = "string", description = "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set.", displayName = "Property Name"),
                     @YamlProperty(name = "resultType", type = "string", description = "Sets the class of the result type (type from output)", displayName = "Result Type"),
-                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim")
+                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim"),
+                    @YamlProperty(name = "variableName", type = "string", description = "Name of variable to use as input, instead of the message body It has as higher precedent if other are set.", displayName = "Variable Name")
             }
     )
     public static class Hl7TerserExpressionDeserializer extends YamlDeserializerBase<Hl7TerserExpression> {
@@ -6924,6 +6925,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     target.setTrim(val);
                     break;
                 }
+                case "variableName": {
+                    String val = asText(node);
+                    target.setVariableName(val);
+                    break;
+                }
                 default: {
                     ExpressionDefinition ed = target.getExpressionType();
                     if (ed != null) {
@@ -7939,7 +7945,8 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "id", type = "string", description = "Sets the id of this node", displayName = "Id"),
                     @YamlProperty(name = "propertyName", type = "string", description = "Name of property to use as input, instead of the message body. It has a lower precedent than the headerName if both are set.", displayName = "Property Name"),
                     @YamlProperty(name = "resultType", type = "string", description = "Sets the class of the result type (type from output)", displayName = "Result Type"),
-                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim")
+                    @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim"),
+                    @YamlProperty(name = "variableName", type = "string", description = "Name of variable to use as input, instead of the message body It has as higher precedent if other are set.", displayName = "Variable Name")
             }
     )
     public static class JqExpressionDeserializer extends YamlDeserializerBase<JqExpression> {
@@ -7992,6 +7999,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     target.setTrim(val);
                     break;
                 }
+                case "variableName": {
+                    String val = asText(node);
+                    target.setVariableName(val);
+                    break;
+                }
                 default: {
                     ExpressionDefinition ed = target.getExpressionType();
                     if (ed != null) {
@@ -8253,6 +8265,7 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     @YamlProperty(name = "suppressExceptions", type = "boolean", description = "Whether to suppress exceptions such as PathNotFoundException.", displayName = "Suppress Exceptions"),
                     @YamlProperty(name = "trim", type = "boolean", description = "Whether to trim the value to remove leading and trailing whitespaces and line breaks", displayName = "Trim"),
                     @YamlProperty(name = "unpackArray", type = "boolean", description = "Whether to unpack a single element json-array into an object.", displayName = "Unpack Array"),
+                    @YamlProperty(name = "variableName", type = "string", description = "Name of variable to use as input, instead of the message body It has as higher precedent if other are set.", displayName = "Variable Name"),
                     @YamlProperty(name = "writeAsString", type = "boolean", description = "Whether to write the output of each row/element as a JSON String value instead of a Map/POJO value.", displayName = "Write As String")
             }
     )
@@ -8331,6 +8344,11 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
                     target.setUnpackArray(val);
                     break;
                 }
+                case "variableName": {
+                    String val = asText(node);
+                    target.setVariableName(val);
+                    break;
+                }
                 case "writeAsString": {
                     String val = asText(node);
                     target.setWriteAsString(val);
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 6b37183070a..fdddfca3136 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
@@ -13063,6 +13063,11 @@
               "type" : "boolean",
               "title" : "Trim",
               "description" : "Whether to trim the value to remove leading and trailing whitespaces and line breaks"
+            },
+            "variableName" : {
+              "type" : "string",
+              "title" : "Variable Name",
+              "description" : "Name of variable to use as input, instead of the message body It has as higher precedent if other are set."
             }
           }
         } ],
@@ -13226,6 +13231,11 @@
               "type" : "boolean",
               "title" : "Trim",
               "description" : "Whether to trim the value to remove leading and trailing whitespaces and line breaks"
+            },
+            "variableName" : {
+              "type" : "string",
+              "title" : "Variable Name",
+              "description" : "Name of variable to use as input, instead of the message body It has as higher precedent if other are set."
             }
           }
         } ],
@@ -13296,6 +13306,11 @@
               "title" : "Unpack Array",
               "description" : "Whether to unpack a single element json-array into an object."
             },
+            "variableName" : {
+              "type" : "string",
+              "title" : "Variable Name",
+              "description" : "Name of variable to use as input, instead of the message body It has as higher precedent if other are set."
+            },
             "writeAsString" : {
               "type" : "boolean",
               "title" : "Write As String",


(camel) 03/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 7fe558b0de3bdfcd39e9f22ce9289cb1b266bdf5
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 10:29:47 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../java/org/apache/camel/builder/Builder.java     |  2 +-
 .../org/apache/camel/builder/DataFormatClause.java |  5 +-
 .../camel/builder/ExpressionClauseSupport.java     |  2 +-
 .../camel/builder/LanguageBuilderFactory.java      |  2 +-
 .../camel/model/dataformat/BeanioDataFormat.java   | 11 +--
 .../camel/model/language/WasmExpression.java       |  2 +-
 .../camel/processor/SendDynamicProcessor.java      | 11 ++-
 .../org/apache/camel/processor/SendProcessor.java  |  7 +-
 .../dataformat/BeanioDataFormatReifier.java        |  4 +-
 .../apache/camel/processor/FromVariableTest.java   |  4 +-
 .../processor/ToDynamicVariableHeadersTest.java    | 93 +++++++++++++++++++++
 .../camel/processor/ToVariableHeadersTest.java     | 94 ++++++++++++++++++++++
 .../org/apache/camel/support/ExchangeHelper.java   |  3 +-
 13 files changed, 220 insertions(+), 20 deletions(-)

diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java
index 042812ab99c..1b0ffec14c8 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/Builder.java
@@ -28,8 +28,8 @@ import org.apache.camel.model.language.JsonPathExpression;
 import org.apache.camel.model.language.LanguageExpression;
 import org.apache.camel.model.language.MethodCallExpression;
 import org.apache.camel.model.language.SimpleExpression;
-import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.VariableExpression;
+import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.util.ObjectHelper;
 
 /**
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
index 63f314a77b9..4855d3502ff 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/DataFormatClause.java
@@ -186,8 +186,9 @@ public class DataFormatClause<T extends ProcessorDefinition<?>> {
     /**
      * Uses the beanio data format
      */
-    public T beanio(String mapping, String streamName, String encoding, boolean ignoreUnidentifiedRecords,
-                    boolean ignoreUnexpectedRecords, boolean ignoreInvalidRecords) {
+    public T beanio(
+            String mapping, String streamName, String encoding, boolean ignoreUnidentifiedRecords,
+            boolean ignoreUnexpectedRecords, boolean ignoreInvalidRecords) {
         BeanioDataFormat dataFormat = new BeanioDataFormat();
         dataFormat.setMapping(mapping);
         dataFormat.setStreamName(streamName);
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
index 1efd4b30997..fd3e07ad584 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClauseSupport.java
@@ -44,8 +44,8 @@ import org.apache.camel.model.language.RefExpression;
 import org.apache.camel.model.language.SimpleExpression;
 import org.apache.camel.model.language.SpELExpression;
 import org.apache.camel.model.language.TokenizerExpression;
-import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.VariableExpression;
+import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.XMLTokenizerExpression;
 import org.apache.camel.model.language.XPathExpression;
 import org.apache.camel.model.language.XQueryExpression;
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java
index 4b225993b8a..d3cd60dc6cd 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/LanguageBuilderFactory.java
@@ -37,8 +37,8 @@ import org.apache.camel.model.language.RefExpression;
 import org.apache.camel.model.language.SimpleExpression;
 import org.apache.camel.model.language.SpELExpression;
 import org.apache.camel.model.language.TokenizerExpression;
-import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.VariableExpression;
+import org.apache.camel.model.language.WasmExpression;
 import org.apache.camel.model.language.XMLTokenizerExpression;
 import org.apache.camel.model.language.XPathExpression;
 import org.apache.camel.model.language.XQueryExpression;
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java
index 744b647454b..0b175a8b50f 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/dataformat/BeanioDataFormat.java
@@ -21,6 +21,7 @@ import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlTransient;
+
 import org.apache.camel.builder.DataFormatBuilder;
 import org.apache.camel.model.DataFormatDefinition;
 import org.apache.camel.spi.Metadata;
@@ -183,8 +184,8 @@ public class BeanioDataFormat extends DataFormatDefinition {
         private String unmarshalSingleObject;
 
         /**
-         * The BeanIO mapping file. Is by default loaded from the classpath. You can prefix with file:, http:, or classpath:
-         * to denote from where to load the mapping file.
+         * The BeanIO mapping file. Is by default loaded from the classpath. You can prefix with file:, http:, or
+         * classpath: to denote from where to load the mapping file.
          */
         public BeanioDataFormat.Builder mapping(String mapping) {
             this.mapping = mapping;
@@ -258,8 +259,8 @@ public class BeanioDataFormat extends DataFormatDefinition {
         }
 
         /**
-         * To use a custom org.apache.camel.dataformat.beanio.BeanIOErrorHandler as error handler while parsing. Configure
-         * the fully qualified class name of the error handler. Notice the options ignoreUnidentifiedRecords,
+         * To use a custom org.apache.camel.dataformat.beanio.BeanIOErrorHandler as error handler while parsing.
+         * Configure the fully qualified class name of the error handler. Notice the options ignoreUnidentifiedRecords,
          * ignoreUnexpectedRecords, and ignoreInvalidRecords may not be in use when you use a custom error handler.
          */
         public BeanioDataFormat.Builder beanReaderErrorHandlerType(String beanReaderErrorHandlerType) {
@@ -293,4 +294,4 @@ public class BeanioDataFormat extends DataFormatDefinition {
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java
index 65c608cb5f5..f1b898477f7 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/WasmExpression.java
@@ -21,6 +21,7 @@ import jakarta.xml.bind.annotation.XmlAccessorType;
 import jakarta.xml.bind.annotation.XmlAttribute;
 import jakarta.xml.bind.annotation.XmlRootElement;
 import jakarta.xml.bind.annotation.XmlTransient;
+
 import org.apache.camel.spi.Metadata;
 
 /**
@@ -42,7 +43,6 @@ public class WasmExpression extends TypedExpressionDefinition {
         super(expression);
     }
 
-
     public WasmExpression(String expression, String module) {
         super(expression);
 
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 19ab67b9421..76633de094d 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
@@ -16,6 +16,8 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.AsyncCallback;
 import org.apache.camel.CamelContext;
 import org.apache.camel.CamelContextAware;
@@ -29,6 +31,7 @@ import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Processor;
 import org.apache.camel.ResolveEndpointFailedException;
 import org.apache.camel.spi.EndpointUtilizationStatistics;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.NormalizedEndpointUri;
 import org.apache.camel.spi.ProducerCache;
@@ -44,8 +47,6 @@ import org.apache.camel.util.URISupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Map;
-
 /**
  * Processor for forwarding exchanges to a dynamic endpoint destination.
  *
@@ -64,6 +65,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
     protected String variableReceive;
     protected ExchangePattern pattern;
     protected ProducerCache producerCache;
+    protected HeadersMapFactory headersMapFactory;
     protected String id;
     protected String routeId;
     protected boolean ignoreInvalidEndpoint;
@@ -183,7 +185,8 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
-                headers = exchange.getMessage().getHeaders();
+                // do a defensive copy of the headers
+                headers = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -379,6 +382,8 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
             }
         }
         ServiceHelper.initService(dynamicAware);
+
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
     }
 
     @Override
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 907482a70e0..d8acea55531 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
@@ -28,6 +28,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.ExchangePattern;
 import org.apache.camel.ExchangePropertyKey;
 import org.apache.camel.Traceable;
+import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.IdAware;
 import org.apache.camel.spi.ProducerCache;
 import org.apache.camel.spi.RouteIdAware;
@@ -57,6 +58,7 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
     protected final ExchangePattern pattern;
     protected ProducerCache producerCache;
     protected AsyncProducer producer;
+    protected HeadersMapFactory headersMapFactory;
     protected final Endpoint destination;
     protected String variableSend;
     protected String variableReceive;
@@ -136,7 +138,8 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
-                headers = exchange.getMessage().getHeaders();
+                // do a defensive copy of the headers
+                headers = headersMapFactory.newMap(exchange.getMessage().getHeaders());
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -294,6 +297,8 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             producerCache = new DefaultProducerCache(this, camelContext, 0);
             // do not add as service as we do not want to manage the producer cache
         }
+
+        headersMapFactory = camelContext.getCamelContextExtension().getHeadersMapFactory();
     }
 
     @Override
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java
index 1e49560407a..bd9708e3612 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/dataformat/BeanioDataFormatReifier.java
@@ -16,12 +16,12 @@
  */
 package org.apache.camel.reifier.dataformat;
 
+import java.util.Map;
+
 import org.apache.camel.CamelContext;
 import org.apache.camel.model.DataFormatDefinition;
 import org.apache.camel.model.dataformat.BeanioDataFormat;
 
-import java.util.Map;
-
 public class BeanioDataFormatReifier extends DataFormatReifier<BeanioDataFormat> {
 
     public BeanioDataFormatReifier(CamelContext camelContext, DataFormatDefinition definition) {
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
index cc1364e31e0..e1227bb9bce 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
@@ -16,13 +16,13 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
+
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
-import java.util.Map;
-
 public class FromVariableTest extends ContextTestSupport {
 
     @Test
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableHeadersTest.java
new file mode 100644
index 00000000000..12e4c1cafc4
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToDynamicVariableHeadersTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class ToDynamicVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    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");
+        getMockEndpoint("mock:result").message(0).header("echo").isEqualTo("CamelCamel");
+
+        template.sendBodyAndHeader("direct:send", "World", "where", "foo");
+
+        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");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBodyAndHeader("direct:receive", "World", "where", "foo");
+
+        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");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBodyAndHeader("direct:sendAndReceive", "World", "where", "foo");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:send")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toD("direct:${header.where}", "hello", null)
+                        .to("mock:result");
+
+                from("direct:receive")
+                        .toD("direct:${header.where}", null, "bye")
+                        .to("mock:after")
+                        .setBody(simple("${variable:bye}"))
+                        .to("mock:result");
+
+                from("direct:sendAndReceive")
+                        .setVariable("hello", simple("Camel"))
+                        .to("mock:before")
+                        .toD("direct:${header.where}", "hello", "bye")
+                        .to("mock:result");
+
+                from("direct:foo")
+                        .setHeader("echo", simple("${body}${body}"))
+                        .transform().simple("Bye ${body}");
+            }
+        };
+    }
+}
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableHeadersTest.java
new file mode 100644
index 00000000000..dbadd6732e7
--- /dev/null
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/ToVariableHeadersTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class ToVariableHeadersTest extends ContextTestSupport {
+
+    @Test
+    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");
+        getMockEndpoint("mock:result").message(0).header("echo").isEqualTo("CamelCamel");
+
+        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:after").message(0).header("echo").isNull();
+        getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        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");
+        getMockEndpoint("mock:result").message(0).header("echo").isNull();
+
+        template.sendBody("direct:sendAndReceive", "World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                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(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")
+                        .setHeader("echo", simple("${body}${body}"))
+                        .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 a3eb8c98162..85c34ecf86a 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
@@ -1117,7 +1117,8 @@ public final class ExchangeHelper {
         }
         Object body = exchange.getMessage().getBody();
         // do a defensive copy of the headers
-        Map<String, Object> map = exchange.getContext().getCamelContextExtension().getHeadersMapFactory().newMap(exchange.getMessage().getHeaders());
+        Map<String, Object> map = exchange.getContext().getCamelContextExtension().getHeadersMapFactory()
+                .newMap(exchange.getMessage().getHeaders());
         if (repo != null) {
             repo.setVariable(name, body);
             repo.setVariable(name + ".headers", map);


(camel) 14/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit c8be513a82afb7bb873c7d13d2864c3ed9cce765
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jan 31 09:21:12 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../main/java/org/apache/camel/reifier/RouteReifier.java  | 15 ++++++---------
 .../java/org/apache/camel/processor/FromVariableTest.java |  6 +++---
 docs/user-manual/modules/ROOT/pages/variables.adoc        |  9 ++++++---
 .../org/apache/camel/dsl/yaml/FromVariableTest.groovy     |  4 ++--
 4 files changed, 17 insertions(+), 17 deletions(-)

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 3bdd8679223..31a92c2582e 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
@@ -45,7 +45,6 @@ import org.apache.camel.reifier.rest.RestBindingReifier;
 import org.apache.camel.spi.CamelInternalProcessorAdvice;
 import org.apache.camel.spi.Contract;
 import org.apache.camel.spi.ErrorHandlerAware;
-import org.apache.camel.spi.HeadersMapFactory;
 import org.apache.camel.spi.InternalProcessor;
 import org.apache.camel.spi.LifecycleStrategy;
 import org.apache.camel.spi.ManagementInterceptStrategy;
@@ -332,7 +331,7 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
 
         // wrap with variable
         if (variable != null) {
-            internal.addAdvice(new VariableAdvice(camelContext, variable));
+            internal.addAdvice(new VariableAdvice(variable));
         }
 
         // and create the route that wraps all of this
@@ -423,23 +422,21 @@ public class RouteReifier extends ProcessorReifier<RouteDefinition> {
     }
 
     /**
-     * Advice for copying the message body into a variable
+     * Advice for moving message body into a variable when using variableReceive mode
      */
     private static class VariableAdvice implements CamelInternalProcessorAdvice<Object> {
 
-        private final HeadersMapFactory factory;
         private final String name;
 
-        public VariableAdvice(CamelContext camelContext, String name) {
-            this.factory = camelContext.getCamelContextExtension().getHeadersMapFactory();
+        public VariableAdvice(String name) {
             this.name = name;
         }
 
         @Override
         public Object before(Exchange exchange) throws Exception {
-            Object body = exchange.getMessage().getBody();
-            ExchangeHelper.setVariable(exchange, name, body);
-            ExchangeHelper.setVariable(exchange, name + ".headers", factory.newMap(exchange.getMessage().getHeaders()));
+            // move body to variable
+            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, name, exchange.getMessage());
+            exchange.getMessage().setBody(null);
             return null;
         }
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
index e1227bb9bce..1d06aeabecb 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/FromVariableTest.java
@@ -27,7 +27,7 @@ public class FromVariableTest extends ContextTestSupport {
 
     @Test
     public void testOriginalBody() throws Exception {
-        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye ");
         getMockEndpoint("mock:result").expectedBodiesReceived("World");
 
         template.sendBody("direct:start", "World");
@@ -37,10 +37,10 @@ public class FromVariableTest extends ContextTestSupport {
 
     @Test
     public void testOriginalHeaders() throws Exception {
-        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye World");
+        getMockEndpoint("mock:foo").expectedBodiesReceived("Bye ");
         getMockEndpoint("mock:foo").expectedHeaderReceived("foo", 456);
         getMockEndpoint("mock:foo").whenAnyExchangeReceived(e -> {
-            Map m = e.getVariable("myKey.headers", Map.class);
+            Map m = e.getVariable("header:myKey", Map.class);
             Assertions.assertNotNull(m);
             Assertions.assertEquals(1, m.size());
             Assertions.assertEquals(123, m.get("foo"));
diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc b/docs/user-manual/modules/ROOT/pages/variables.adoc
index e392cd26fcc..23b7d92ad00 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -336,10 +336,10 @@ myVar.header.level=gold
 IMPORTANT: Notice the headers are stored with the syntax `variable.header.key`. In the example above the variable name is `myVar`,
 and the header key is `level`, which gives: `myVar.header.level`.
 
-=== Using variable to store a copy of the incoming message body
+=== Using variable to store incoming message body
 
-You can configure the `from` to store a copy of the message body into a variable. This makes it easy to have quick access
-to the original incoming message body via the variable.
+You can configure the `from` to store the message body into a variable, instead of the `Message`. This makes it easy to have quick access
+to the original incoming message body via the variable. Notice that the body on the `Message` will be `null`.
 
 The following example from a unit test shows how to do this. Notice how Java DSL uses `fromV` to make it possible to specify
 the name of the variable. In XML and YAML DSL you specify this using the `variableReceive` parameter.
@@ -389,6 +389,9 @@ from:
 ----
 ====
 
+NOTE: In the examples above the transform `Bye $\{body}` will result as `Bye ` because the `Message` has no message body, as the incoming
+message body is stored in the variable `myKey` instead.
+
 === Using variables when sending and receiving messages to an endpoint
 
 You can configure the `to` to use variables for any of the following (or both) when sending and receiving:
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
index 5984b103e00..5112cb1a9ca 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/FromVariableTest.groovy
@@ -44,9 +44,9 @@ class FromVariableTest extends YamlTestSupport {
             '''
 
             withMock('mock:foo') {
-                expectedBodiesReceived 'Bye World'
+                expectedBodiesReceived 'Bye '
                 whenAnyExchangeReceived { e -> {
-                    Map m = e.getVariable("myKey.headers", Map.class)
+                    Map m = e.getVariable("header:myKey", Map.class)
                     Assertions.assertNotNull(m)
                     Assertions.assertEquals(1, m.size())
                     Assertions.assertEquals(123, m.get("foo"))


(camel) 12/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a6ea966df852c01f1b1808f1cc95af1ac1d741df
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Jan 30 14:46:11 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../org/apache/camel/language/jq/JqFunctions.java  |  2 +-
 .../language/jq/JqSimpleTransformVariableTest.java | 54 ++++++++++++++
 .../modules/languages/pages/simple-language.adoc   | 12 ++++
 .../simple/ast/SimpleFunctionExpression.java       | 18 +++++
 .../org/apache/camel/language/VariableTest.java    | 16 +++--
 .../processor/PollEnrichVariableHeadersTest.java   |  2 +-
 .../org/apache/camel/support/AbstractExchange.java |  3 +-
 .../org/apache/camel/support/ExchangeHelper.java   | 14 +++-
 .../camel/support/ExchangeVariableRepository.java  | 82 +++++++++++++++++-----
 ...pository.java => HeaderVariableRepository.java} | 12 ++--
 .../camel/support/builder/ExpressionBuilder.java   | 65 +++++++++++++++++
 11 files changed, 245 insertions(+), 35 deletions(-)

diff --git a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
index 82d5ce182fa..8589ed7eeeb 100644
--- a/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
+++ b/components/camel-jq/src/main/java/org/apache/camel/language/jq/JqFunctions.java
@@ -181,7 +181,7 @@ public final class JqFunctions {
      *
      * <pre>
      * {@code
-     * .name = proeprty(\"CommitterName\")"
+     * .name = property(\"CommitterName\")"
      * }
      * </pre>
      *
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformVariableTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformVariableTest.java
new file mode 100644
index 00000000000..5e62dbad94f
--- /dev/null
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformVariableTest.java
@@ -0,0 +1,54 @@
+/*
+ * 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.language.jq;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Test;
+
+public class JqSimpleTransformVariableTest extends JqTestSupport {
+
+    private static String EXPECTED = """
+            {
+              "country": "se",
+            }""";
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        .setVariable("place", constant("{ \"name\": \"sweden\", \"iso\": \"se\" }"))
+                        .transform().simple("""
+                                {
+                                  "country": "${jq(variable:place,.iso)}",
+                                }""")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testTransform() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived(EXPECTED);
+
+        template.sendBody("direct:start", "{\"id\": 123, \"age\": 42, \"name\": \"scott\"}");
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+}
diff --git a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
index 9a663faf367..2036d642303 100644
--- a/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
+++ b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc
@@ -274,12 +274,24 @@ The algorithm can be SHA-256 (default) or SHA3-256.
 |jsonpath(exp) | Object | When working with JSon data, then this allows to use the JsonPath language
 for example to extract data from the message body (in JSon format). This requires having camel-jsonpath JAR on the classpath.
 
+|jsonpath(input,exp) | Object | When working with JSon data, then this allows to use the JsonPath language
+for example to extract data from the message body (in JSon format). This requires having camel-jsonpath JAR on the classpath.
+For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
+
 |jq(exp) | Object | When working with JSon data, then this allows to use the JQ language
 for example to extract data from the message body (in JSon format). This requires having camel-jq JAR on the classpath.
 
+|jq(input,exp) | Object | When working with JSon data, then this allows to use the JQ language
+for example to extract data from the message body (in JSon format). This requires having camel-jq JAR on the classpath.
+For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
+
 |xpath(exp) | Object | When working with XML data, then this allows to use the XPath language
 for example to extract data from the message body (in XML format). This requires having camel-xpath JAR on the classpath.
 
+|xpath(input,exp) | Object | When working with XML data, then this allows to use the XPath language
+for example to extract data from the message body (in XML format). This requires having camel-xpath JAR on the classpath.
+For _input_ you can choose `header:key`, `exchangeProperty:key` or `variable:key` to use as input for the JSon payload instead of the message body.
+
 |=======================================================================
 
 == OGNL expression support
diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
index 20b07a3d12b..5572e108b0a 100644
--- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
+++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionExpression.java
@@ -522,6 +522,12 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${jq(exp)} was: " + function, token.getIndex());
             }
             exp = StringHelper.removeQuotes(exp);
+            if (exp.startsWith("header:") || exp.startsWith("property:") || exp.startsWith("exchangeProperty:")
+                    || exp.startsWith("variable:")) {
+                String input = StringHelper.before(exp, ",");
+                exp = StringHelper.after(exp, ",");
+                return ExpressionBuilder.singleInputLanguageExpression("jq", exp, input);
+            }
             return ExpressionBuilder.languageExpression("jq", exp);
         }
         // jsonpath
@@ -532,6 +538,12 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${jsonpath(exp)} was: " + function, token.getIndex());
             }
             exp = StringHelper.removeQuotes(exp);
+            if (exp.startsWith("header:") || exp.startsWith("property:") || exp.startsWith("exchangeProperty:")
+                    || exp.startsWith("variable:")) {
+                String input = StringHelper.before(exp, ",");
+                exp = StringHelper.after(exp, ",");
+                return ExpressionBuilder.singleInputLanguageExpression("jq", exp, input);
+            }
             return ExpressionBuilder.languageExpression("jsonpath", exp);
         }
         remainder = ifStartsWithReturnRemainder("xpath(", function);
@@ -541,6 +553,12 @@ public class SimpleFunctionExpression extends LiteralExpression {
                 throw new SimpleParserException("Valid syntax: ${xpath(exp)} was: " + function, token.getIndex());
             }
             exp = StringHelper.removeQuotes(exp);
+            if (exp.startsWith("header:") || exp.startsWith("property:") || exp.startsWith("exchangeProperty:")
+                    || exp.startsWith("variable:")) {
+                String input = StringHelper.before(exp, ",");
+                exp = StringHelper.after(exp, ",");
+                return ExpressionBuilder.singleInputLanguageExpression("jq", exp, input);
+            }
             return ExpressionBuilder.languageExpression("xpath", exp);
         }
 
diff --git a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
index 6e28f0116f1..8a0de284b32 100644
--- a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
@@ -20,6 +20,7 @@ import java.util.Map;
 
 import org.apache.camel.LanguageTestSupport;
 import org.apache.camel.language.variable.VariableLanguage;
+import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -45,20 +46,25 @@ public class VariableTest extends LanguageTestSupport {
 
     @Test
     public void testVariableHeaders() throws Exception {
-        exchange.setVariable("myKey.header.foo", "abc");
-        exchange.setVariable("myKey.header.bar", 123);
+        exchange.setVariable("header:myKey.foo", "abc");
+        exchange.setVariable("header:myKey.bar", 123);
         exchange.setVariable("myOtherKey", "Hello Again");
 
-        assertExpression("myKey.header.foo", "abc");
-        assertExpression("myKey.header.bar", 123);
+        assertEquals("abc", exchange.getVariable("header:myKey.foo"));
+        assertEquals(123, exchange.getVariable("header:myKey.bar"));
 
-        Map map = exchange.getVariable("myKey.headers", Map.class);
+        Map map = exchange.getVariable("header:myKey", Map.class);
         assertNotNull(map);
         assertEquals(2, map.size());
         assertEquals("abc", map.get("foo"));
         assertEquals(123, map.get("bar"));
     }
 
+    @Test
+    public void testInvalidHeaderKey() {
+        Assertions.assertThrows(IllegalArgumentException.class, () -> exchange.getVariable("header:"));
+    }
+
     @Test
     public void testSingleton() {
         VariableLanguage prop = new VariableLanguage();
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
index e754ec464ac..cadd647e286 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
@@ -30,7 +30,7 @@ public class PollEnrichVariableHeadersTest extends ContextTestSupport {
         getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
         getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
         getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
-        getMockEndpoint("mock:result").expectedVariableReceived("bye.header.echo", "CamelCamel");
+        getMockEndpoint("mock:result").expectedVariableReceived("header:bye.echo", "CamelCamel");
         getMockEndpoint("mock:result").message(0).header("echo").isNull();
 
         template.sendBody("direct:receive", "World");
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
index 4a7f67fb68f..a0367cd5400 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/AbstractExchange.java
@@ -128,8 +128,7 @@ abstract class AbstractExchange implements Exchange {
             if (this.variableRepository == null) {
                 this.variableRepository = new ExchangeVariableRepository(getContext());
             }
-            this.variableRepository.setVariables(parent.getVariables());
-
+            this.variableRepository.copyFrom(parent.variableRepository);
         }
         if (parent.hasProperties()) {
             this.properties = safeCopyProperties(parent.properties);
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 6ba9fc6bb02..7dd9f30b93e 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
@@ -1088,6 +1088,10 @@ public final class ExchangeHelper {
     public static void setVariable(Exchange exchange, String name, Object value) {
         VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
+        // header and exchange is reserved
+        if ("header".equals(id) || "exchange".equals(id)) {
+            id = null;
+        }
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
@@ -1113,6 +1117,10 @@ public final class ExchangeHelper {
     public static void setVariableFromMessageBodyAndHeaders(Exchange exchange, String name, Message message) {
         VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
+        // header and exchange is reserved
+        if ("header".equals(id) || "exchange".equals(id)) {
+            id = null;
+        }
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
@@ -1128,7 +1136,7 @@ public final class ExchangeHelper {
         Object body = message.getBody();
         va.setVariable(name, body);
         for (Map.Entry<String, Object> header : message.getHeaders().entrySet()) {
-            String key = name + ".header." + header.getKey();
+            String key = "header:" + name + "." + header.getKey();
             Object value = header.getValue();
             va.setVariable(key, value);
         }
@@ -1145,6 +1153,10 @@ public final class ExchangeHelper {
     public static Object getVariable(Exchange exchange, String name) {
         VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
+        // header and exchange is reserved
+        if ("header".equals(id) || "exchange".equals(id)) {
+            id = null;
+        }
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
index 1d75855542f..0e144ce3627 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
@@ -17,10 +17,10 @@
 package org.apache.camel.support;
 
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
-import org.apache.camel.StreamCache;
 import org.apache.camel.spi.VariableRepository;
 import org.apache.camel.support.service.ServiceHelper;
 import org.apache.camel.util.CaseInsensitiveMap;
@@ -31,38 +31,82 @@ import org.apache.camel.util.StringHelper;
  */
 final class ExchangeVariableRepository extends AbstractVariableRepository {
 
+    private final Map<String, Object> headers = new ConcurrentHashMap<>(8);
+
     public ExchangeVariableRepository(CamelContext camelContext) {
         setCamelContext(camelContext);
         // ensure its started
         ServiceHelper.startService(this);
     }
 
-    @Override
-    public String getId() {
-        return "exchange";
+    void copyFrom(ExchangeVariableRepository source) {
+        setVariables(source.getVariables());
+        this.headers.putAll(source.headers);
     }
 
     @Override
     public Object getVariable(String name) {
-        Object answer = super.getVariable(name);
-        if (answer == null && name.endsWith(".headers")) {
-            String prefix = name.substring(0, name.length() - 1) + "."; // xxx.headers -> xxx.header.
-            // we want all headers for a given variable
-            Map<String, Object> map = new CaseInsensitiveMap();
-            for (Map.Entry<String, Object> entry : getVariables().entrySet()) {
-                String key = entry.getKey();
-                if (key.startsWith(prefix)) {
-                    key = StringHelper.after(key, prefix);
-                    map.put(key, entry.getValue());
+        String id = StringHelper.before(name, ":");
+        if ("header".equals(id)) {
+            String prefix = StringHelper.after(name, ":");
+            if (prefix == null || prefix.isBlank()) {
+                throw new IllegalArgumentException("Variable " + name + " must have header key");
+            }
+            if (!prefix.contains(".")) {
+                prefix = prefix + ".";
+                // we want all headers for a given variable
+                Map<String, Object> map = new CaseInsensitiveMap();
+                for (Map.Entry<String, Object> entry : headers.entrySet()) {
+                    String key = entry.getKey();
+                    if (key.startsWith(prefix)) {
+                        key = StringHelper.after(key, prefix);
+                        map.put(key, entry.getValue());
+                    }
                 }
+                return map;
+            } else {
+                return headers.get(prefix);
             }
-            return map;
         }
-        if (answer instanceof StreamCache sc) {
-            // reset so the cache is ready to be used as a variable
-            sc.reset();
+        return super.getVariable(name);
+    }
+
+    @Override
+    public void setVariable(String name, Object value) {
+        String id = StringHelper.before(name, ":");
+        if ("header".equals(id)) {
+            name = StringHelper.after(name, ":");
+            if (value != null) {
+                // avoid the NullPointException
+                headers.put(name, value);
+            } else {
+                // if the value is null, we just remove the key from the map
+                headers.remove(name);
+            }
+        } else {
+            super.setVariable(name, value);
         }
-        return answer;
+    }
+
+    @Override
+    public Object removeVariable(String name) {
+        String id = StringHelper.before(name, ":");
+        if ("header".equals(id)) {
+            name = StringHelper.after(name, ":");
+            return headers.remove(name);
+        }
+        return super.removeVariable(name);
+    }
+
+    @Override
+    public void clear() {
+        super.clear();
+        headers.clear();
+    }
+
+    @Override
+    public String getId() {
+        return "exchange";
     }
 
 }
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/HeaderVariableRepository.java
similarity index 85%
copy from core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
copy to core/camel-support/src/main/java/org/apache/camel/support/HeaderVariableRepository.java
index 1d75855542f..21a691db9b7 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/HeaderVariableRepository.java
@@ -27,11 +27,11 @@ import org.apache.camel.util.CaseInsensitiveMap;
 import org.apache.camel.util.StringHelper;
 
 /**
- * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables.
+ * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables for message headers.
  */
-final class ExchangeVariableRepository extends AbstractVariableRepository {
+final class HeaderVariableRepository extends AbstractVariableRepository {
 
-    public ExchangeVariableRepository(CamelContext camelContext) {
+    public HeaderVariableRepository(CamelContext camelContext) {
         setCamelContext(camelContext);
         // ensure its started
         ServiceHelper.startService(this);
@@ -39,14 +39,14 @@ final class ExchangeVariableRepository extends AbstractVariableRepository {
 
     @Override
     public String getId() {
-        return "exchange";
+        return "header";
     }
 
     @Override
     public Object getVariable(String name) {
         Object answer = super.getVariable(name);
-        if (answer == null && name.endsWith(".headers")) {
-            String prefix = name.substring(0, name.length() - 1) + "."; // xxx.headers -> xxx.header.
+        if (answer == null && !name.contains(".")) {
+            String prefix = name + ".";
             // we want all headers for a given variable
             Map<String, Object> map = new CaseInsensitiveMap();
             for (Map.Entry<String, Object> entry : getVariables().entrySet()) {
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
index 6ea8d094b0b..bc9775de5e5 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java
@@ -53,6 +53,7 @@ import org.apache.camel.support.GroupIterator;
 import org.apache.camel.support.GroupTokenIterator;
 import org.apache.camel.support.LanguageHelper;
 import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.SingleInputTypedLanguageSupport;
 import org.apache.camel.util.InetAddressUtil;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
@@ -957,6 +958,70 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an expression for evaluating the expression/predicate using the given language
+     *
+     * @param  expression the expression or predicate
+     * @param  input      input to use instead of message body
+     * @return            an expression object which will evaluate the expression/predicate using the given language
+     */
+    public static Expression singleInputLanguageExpression(final String language, final String expression, final String input) {
+        return new ExpressionAdapter() {
+            private Expression expr;
+            private Predicate pred;
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                return expr.evaluate(exchange, Object.class);
+            }
+
+            @Override
+            public boolean matches(Exchange exchange) {
+                return pred.matches(exchange);
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                super.init(context);
+                Language lan = context.resolveLanguage(language);
+                if (lan != null) {
+                    if (input != null && lan instanceof SingleInputTypedLanguageSupport sil) {
+                        String prefix = StringHelper.before(input, ":");
+                        String source = StringHelper.after(input, ":");
+                        if (prefix != null) {
+                            prefix = prefix.trim();
+                        }
+                        if (source != null) {
+                            source = source.trim();
+                        }
+                        if ("header".equals(prefix)) {
+                            sil.setHeaderName(source);
+                        } else if ("property".equals(prefix) || "exchangeProperty".equals(prefix)) {
+                            sil.setPropertyName(source);
+                        } else if ("variable".equals(prefix)) {
+                            sil.setVariableName(source);
+                        } else {
+                            throw new IllegalArgumentException(
+                                    "Invalid input source for language. Should either be header:key, exchangeProperty:key, or variable:key, was: "
+                                                               + input);
+                        }
+                    }
+                    pred = lan.createPredicate(expression);
+                    pred.init(context);
+                    expr = lan.createExpression(expression);
+                    expr.init(context);
+                } else {
+                    throw new NoSuchLanguageException(language);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "language[" + language + ":" + expression + "]";
+            }
+        };
+    }
+
     /**
      * Returns the expression for the exchanges inbound message body
      */


(camel) 06/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit a7dca4d3ba2ebf1109f28e89e181fe2b4d998d9e
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 13:10:03 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../src/main/java/org/apache/camel/Exchange.java   |  2 +-
 .../VariableRepository.java => VariableAware.java} | 25 ++---------
 .../org/apache/camel/spi/VariableRepository.java   | 11 +----
 .../java/org/apache/camel/processor/Enricher.java  |  3 +-
 .../org/apache/camel/processor/PollEnricher.java   |  2 +-
 .../camel/processor/SendDynamicProcessor.java      |  2 +-
 .../org/apache/camel/processor/SendProcessor.java  | 13 +++---
 .../processor/PollEnrichVariableHeadersTest.java   | 10 +----
 .../org/apache/camel/support/ExchangeHelper.java   | 48 ++++++++++++----------
 docs/user-manual/modules/ROOT/pages/variables.adoc |  4 +-
 10 files changed, 49 insertions(+), 71 deletions(-)

diff --git a/core/camel-api/src/main/java/org/apache/camel/Exchange.java b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
index 3aa5175e076..3495a865391 100644
--- a/core/camel-api/src/main/java/org/apache/camel/Exchange.java
+++ b/core/camel-api/src/main/java/org/apache/camel/Exchange.java
@@ -64,7 +64,7 @@ import org.apache.camel.spi.annotations.ConstantProvider;
  * details.
  */
 @ConstantProvider("org.apache.camel.ExchangeConstantProvider")
-public interface Exchange {
+public interface Exchange extends VariableAware {
 
     String AUTHENTICATION = "CamelAuthentication";
     String AUTHENTICATION_FAILURE_POLICY_ID = "CamelAuthenticationFailurePolicyId";
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java b/core/camel-api/src/main/java/org/apache/camel/VariableAware.java
similarity index 61%
copy from core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
copy to core/camel-api/src/main/java/org/apache/camel/VariableAware.java
index 153b3502135..ab82303117c 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
+++ b/core/camel-api/src/main/java/org/apache/camel/VariableAware.java
@@ -14,27 +14,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.camel.spi;
-
-import org.apache.camel.StaticService;
+package org.apache.camel;
 
 /**
- * Repository for storing and accessing variables.
+ * An interface to represent an object that supports variables.
  */
-public interface VariableRepository extends StaticService {
-
-    /**
-     * The id of this repository.
-     */
-    String getId();
+public interface VariableAware {
 
     /**
      * Returns a variable by name.
      *
-     * If the variable is of type {@link org.apache.camel.StreamCache} then the repository should ensure to reset the
-     * stream cache before returning the value, to ensure the content can be read by the Camel end user and would be
-     * re-readable next time.
-     *
      * @param  name the name of the variable
      * @return      the value of the given variable or <tt>null</tt> if there is no variable for the given name
      */
@@ -48,12 +37,4 @@ public interface VariableRepository extends StaticService {
      */
     void setVariable(String name, Object value);
 
-    /**
-     * Removes the given variable
-     *
-     * @param  name of the variable
-     * @return      the old value of the variable, or <tt>null</tt> if there was no variable for the given name
-     */
-    Object removeVariable(String name);
-
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
index 153b3502135..fba280fcda1 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/VariableRepository.java
@@ -17,11 +17,12 @@
 package org.apache.camel.spi;
 
 import org.apache.camel.StaticService;
+import org.apache.camel.VariableAware;
 
 /**
  * Repository for storing and accessing variables.
  */
-public interface VariableRepository extends StaticService {
+public interface VariableRepository extends StaticService, VariableAware {
 
     /**
      * The id of this repository.
@@ -40,14 +41,6 @@ public interface VariableRepository extends StaticService {
      */
     Object getVariable(String name);
 
-    /**
-     * Sets a variable
-     *
-     * @param name  of the variable
-     * @param value the value of the variable
-     */
-    void setVariable(String name, Object value);
-
     /**
      * Removes the given variable
      *
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 9d641b01a05..5dcabc96fac 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
@@ -224,7 +224,8 @@ public class Enricher extends AsyncProcessorSupport implements IdAware, RouteIdA
                         if (aggregatedExchange != null) {
                             if (variableReceive != null) {
                                 // result should be stored in variable instead of message body
-                                ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                                ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
+                                        exchange.getMessage());
                                 exchange.getMessage().setBody(originalBody);
                                 exchange.getMessage().setHeaders(originalHeaders);
                             }
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 4005d83834c..d939963dd26 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
@@ -352,7 +352,7 @@ public class PollEnricher extends AsyncProcessorSupport implements IdAware, Rout
                 if (aggregatedExchange != null) {
                     if (variableReceive != null) {
                         // result should be stored in variable instead of message body
-                        ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                        ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive, exchange.getMessage());
                         exchange.getMessage().setBody(originalBody);
                         exchange.getMessage().setHeaders(originalHeaders);
                     }
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 76633de094d..d877f180684 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
@@ -238,7 +238,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
                 }
                 // result should be stored in variable instead of message body
                 if (variableReceive != null) {
-                    ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                    ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive, exchange.getMessage());
                     exchange.getMessage().setBody(originalBody);
                     exchange.getMessage().setHeaders(originalHeaders);
                 }
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 d8acea55531..b04e75a88fb 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
@@ -131,11 +131,10 @@ 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
+        // when using variables then we need to remember original data
         Object body = null;
         Map<String, Object> headers = null;
-        if (variableReceive != null) {
+        if (variableSend != null || variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
                 // do a defensive copy of the headers
@@ -181,7 +180,8 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
                     try {
                         // result should be stored in variable instead of message body/headers
                         if (variableReceive != null) {
-                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
+                                    exchange.getMessage());
                             exchange.getMessage().setBody(originalBody);
                             exchange.getMessage().setHeaders(originalHeaders);
                         }
@@ -202,6 +202,8 @@ 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);
@@ -239,7 +241,8 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
                         exchange.setPattern(existingPattern);
                         // result should be stored in variable instead of message body/headers
                         if (variableReceive != null) {
-                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive,
+                                    exchange.getMessage());
                             exchange.getMessage().setBody(originalBody);
                             exchange.getMessage().setHeaders(originalHeaders);
                         }
diff --git a/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
index 852e3b0cd13..e754ec464ac 100644
--- a/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/processor/PollEnrichVariableHeadersTest.java
@@ -16,11 +16,8 @@
  */
 package org.apache.camel.processor;
 
-import java.util.Map;
-
 import org.apache.camel.ContextTestSupport;
 import org.apache.camel.builder.RouteBuilder;
-import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 public class PollEnrichVariableHeadersTest extends ContextTestSupport {
@@ -33,13 +30,8 @@ public class PollEnrichVariableHeadersTest extends ContextTestSupport {
         getMockEndpoint("mock:after").expectedVariableReceived("bye", "Bye World");
         getMockEndpoint("mock:result").expectedBodiesReceived("Bye World");
         getMockEndpoint("mock:result").expectedVariableReceived("bye", "Bye World");
+        getMockEndpoint("mock:result").expectedVariableReceived("bye.header.echo", "CamelCamel");
         getMockEndpoint("mock:result").message(0).header("echo").isNull();
-        getMockEndpoint("mock:result").whenAnyExchangeReceived(e -> {
-            Map m = e.getVariable("bye.headers", Map.class);
-            Assertions.assertNotNull(m);
-            Assertions.assertEquals(1, m.size());
-            Assertions.assertEquals("CamelCamel", m.get("echo"));
-        });
 
         template.sendBody("direct:receive", "World");
 
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 02df6ccce7b..6ba9fc6bb02 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
@@ -47,6 +47,7 @@ import org.apache.camel.NoTypeConversionAvailableException;
 import org.apache.camel.Route;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.TypeConversionException;
+import org.apache.camel.VariableAware;
 import org.apache.camel.WrappedFile;
 import org.apache.camel.spi.NormalizedEndpointUri;
 import org.apache.camel.spi.UnitOfWork;
@@ -1085,23 +1086,31 @@ public final class ExchangeHelper {
      * @param value    the value of the variable
      */
     public static void setVariable(Exchange exchange, String name, Object value) {
+        VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
-            VariableRepository repo = factory.getVariableRepository(id);
+            repo = factory.getVariableRepository(id);
             if (repo != null) {
                 name = StringHelper.after(name, ":");
-                repo.setVariable(name, value);
             } else {
                 throw new IllegalArgumentException("VariableRepository with id: " + id + " does not exist");
             }
-        } else {
-            exchange.setVariable(name, value);
         }
+        VariableAware va = repo != null ? repo : exchange;
+        va.setVariable(name, value);
     }
 
-    public static void setVariableFromMessageBodyAndHeaders(Exchange exchange, String name) {
+    /**
+     * Sets the variable from the given message body and headers
+     *
+     * @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 message  the message with the body and headers as source values
+     */
+    public static void setVariableFromMessageBodyAndHeaders(Exchange exchange, String name, Message message) {
         VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
         if (id != null) {
@@ -1113,16 +1122,15 @@ public final class ExchangeHelper {
             }
             name = StringHelper.after(name, ":");
         }
-        Object body = exchange.getMessage().getBody();
-        // do a defensive copy of the headers
-        Map<String, Object> map = exchange.getContext().getCamelContextExtension().getHeadersMapFactory()
-                .newMap(exchange.getMessage().getHeaders());
-        if (repo != null) {
-            repo.setVariable(name, body);
-            repo.setVariable(name + ".headers", map);
-        } else {
-            exchange.setVariable(name, body);
-            exchange.setVariable(name + ".headers", map);
+        VariableAware va = repo != null ? repo : exchange;
+
+        // set body and headers as variables
+        Object body = message.getBody();
+        va.setVariable(name, body);
+        for (Map.Entry<String, Object> header : message.getHeaders().entrySet()) {
+            String key = name + ".header." + header.getKey();
+            Object value = header.getValue();
+            va.setVariable(key, value);
         }
     }
 
@@ -1135,22 +1143,20 @@ public final class ExchangeHelper {
      * @return          the variable
      */
     public static Object getVariable(Exchange exchange, String name) {
-        Object answer;
+        VariableRepository repo = null;
         String id = StringHelper.before(name, ":");
         if (id != null) {
             VariableRepositoryFactory factory
                     = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
-            VariableRepository repo = factory.getVariableRepository(id);
+            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;
+        VariableAware va = repo != null ? repo : exchange;
+        return va.getVariable(name);
     }
 
     /**
diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc b/docs/user-manual/modules/ROOT/pages/variables.adoc
index f52dbcc9687..c277a9b37ec 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -314,9 +314,11 @@ And the variable contains all the data received from the remote HTTP service sep
 [source,properties]
 ----
 myVar=Bye World
-myVar.headers.level=gold
+myVar.header.level=gold
 ----
 
+IMPORTANT: Notice the headers are stored with the syntax `variable.header.key`. In the example above the variable name is `myVar`,
+and the header key is `level`, which gives: `myVar.header.level`.
 
 === Using variable to store a copy of the incoming message body
 


(camel) 07/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6217381bbd2c1f048bdf50e463aed01bfd1ec52d
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 14:03:11 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../java/org/apache/camel/language/VariableTest.java | 20 ++++++++++++++++++++
 .../camel/support/ExchangeVariableRepository.java    | 15 +++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
index 95486d2a77d..41eeb16aa1b 100644
--- a/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
+++ b/core/camel-core/src/test/java/org/apache/camel/language/VariableTest.java
@@ -20,6 +20,10 @@ import org.apache.camel.LanguageTestSupport;
 import org.apache.camel.language.variable.VariableLanguage;
 import org.junit.jupiter.api.Test;
 
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 public class VariableTest extends LanguageTestSupport {
@@ -39,6 +43,22 @@ public class VariableTest extends LanguageTestSupport {
         assertPredicate("varLocal", false);
     }
 
+    @Test
+    public void testVariableHeaders() throws Exception {
+        exchange.setVariable("myKey.header.foo", "abc");
+        exchange.setVariable("myKey.header.bar", 123);
+        exchange.setVariable("myOtherKey", "Hello Again");
+
+        assertExpression("myKey.header.foo", "abc");
+        assertExpression("myKey.header.bar", 123);
+
+        Map map = exchange.getVariable("myKey.headers", Map.class);
+        assertNotNull(map);
+        assertEquals(2, map.size());
+        assertEquals("abc", map.get("foo"));
+        assertEquals(123, map.get("bar"));
+    }
+
     @Test
     public void testSingleton() {
         VariableLanguage prop = new VariableLanguage();
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
index 14f74be0760..b913ef26056 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/ExchangeVariableRepository.java
@@ -26,6 +26,8 @@ import org.apache.camel.StreamCache;
 import org.apache.camel.spi.BrowsableVariableRepository;
 import org.apache.camel.spi.VariableRepository;
 import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.CaseInsensitiveMap;
+import org.apache.camel.util.StringHelper;
 
 /**
  * {@link VariableRepository} which is local per {@link Exchange} to hold request-scoped variables.
@@ -42,6 +44,19 @@ class ExchangeVariableRepository extends ServiceSupport implements BrowsableVari
     @Override
     public Object getVariable(String name) {
         Object answer = variables.get(name);
+        if (answer == null && name.endsWith(".headers")) {
+            String prefix = name.substring(0, name.length() - 1) + "."; // xxx.headers -> xxx.header.
+            // we want all headers for a given variable
+            Map<String, Object> map = new CaseInsensitiveMap();
+            for (Map.Entry<String, Object> entry : variables.entrySet()) {
+                String key = entry.getKey();
+                if (key.startsWith(prefix)) {
+                    key = StringHelper.after(key, prefix);
+                    map.put(key, entry.getValue());
+                }
+            }
+            return map;
+        }
         if (answer instanceof StreamCache sc) {
             // reset so the cache is ready to be used as a variable
             sc.reset();


(camel) 02/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 0cba30cf1abdf61a06f8f85e0c730fd09a61ca5b
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 10:12:58 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../camel/processor/SendDynamicProcessor.java      | 50 +++++++++++-----------
 .../org/apache/camel/processor/SendProcessor.java  | 18 ++++----
 .../org/apache/camel/support/ExchangeHelper.java   | 25 +++++++++++
 .../camel/support/processor/MarshalProcessor.java  |  1 -
 .../support/processor/UnmarshalProcessor.java      |  1 -
 5 files changed, 60 insertions(+), 35 deletions(-)

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 13620cc6ce6..19ab67b9421 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
@@ -44,6 +44,8 @@ import org.apache.camel.util.URISupport;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Map;
+
 /**
  * Processor for forwarding exchanges to a dynamic endpoint destination.
  *
@@ -101,8 +103,6 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
 
     @Override
     public boolean process(Exchange exchange, final AsyncCallback callback) {
-        // TODO: variables
-
         if (!isStarted()) {
             exchange.setException(new IllegalStateException("SendProcessor has not been started: " + this));
             callback.done(true);
@@ -179,9 +179,11 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object body = null;
+        Map<String, Object> headers = null;
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
+                headers = exchange.getMessage().getHeaders();
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -189,6 +191,7 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
             }
         }
         final Object originalBody = body;
+        final Map<String, Object> originalHeaders = headers;
 
         // send the exchange to the destination using the producer cache
         final Processor preProcessor = preAwareProcessor;
@@ -204,7 +207,6 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
                 }
                 // replace message body with variable
                 if (variableSend != null) {
-                    // it may be a global variable
                     Object value = ExchangeHelper.getVariable(exchange, variableSend);
                     exchange.getMessage().setBody(value);
                 }
@@ -217,30 +219,28 @@ public class SendDynamicProcessor extends AsyncProcessorSupport implements IdAwa
             }
 
             LOG.debug(">>>> {} {}", endpoint, e);
-            return p.process(target, new AsyncCallback() {
-                public void done(boolean doneSync) {
-                    // restore previous MEP
-                    target.setPattern(existingPattern);
-                    try {
-                        if (postProcessor != null) {
-                            postProcessor.process(target);
-                        }
-                    } catch (Exception e) {
-                        target.setException(e);
-                    }
-                    // stop endpoint if prototype as it was only used once
-                    if (stopEndpoint) {
-                        ServiceHelper.stopAndShutdownService(endpoint);
-                    }
-                    // 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);
+            return p.process(target, doneSync -> {
+                // restore previous MEP
+                target.setPattern(existingPattern);
+                try {
+                    if (postProcessor != null) {
+                        postProcessor.process(target);
                     }
-                    // signal we are done
-                    c.done(doneSync);
+                } catch (Exception e1) {
+                    target.setException(e1);
+                }
+                // stop endpoint if prototype as it was only used once
+                if (stopEndpoint) {
+                    ServiceHelper.stopAndShutdownService(endpoint);
+                }
+                // result should be stored in variable instead of message body
+                if (variableReceive != null) {
+                    ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
+                    exchange.getMessage().setBody(originalBody);
+                    exchange.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 960450d8d44..907482a70e0 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
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.processor;
 
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicLong;
 
 import org.apache.camel.AsyncCallback;
@@ -131,9 +132,11 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
         // if we should store the received message body in a variable,
         // then we need to preserve the original message body
         Object body = null;
+        Map<String, Object> headers = null;
         if (variableReceive != null) {
             try {
                 body = exchange.getMessage().getBody();
+                headers = exchange.getMessage().getHeaders();
             } catch (Exception throwable) {
                 exchange.setException(throwable);
                 callback.done(true);
@@ -141,6 +144,7 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             }
         }
         final Object originalBody = body;
+        final Map<String, Object> originalHeaders = headers;
 
         if (extendedStatistics) {
             counter.incrementAndGet();
@@ -172,11 +176,11 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
             if (newCallback) {
                 ac = doneSync -> {
                     try {
-                        // result should be stored in variable instead of message body
+                        // result should be stored in variable instead of message body/headers
                         if (variableReceive != null) {
-                            Object value = exchange.getMessage().getBody();
-                            ExchangeHelper.setVariable(exchange, variableReceive, value);
+                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
                             exchange.getMessage().setBody(originalBody);
+                            exchange.getMessage().setHeaders(originalHeaders);
                         }
                         // restore previous MEP
                         target.setPattern(existingPattern);
@@ -193,7 +197,6 @@ 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);
                 }
@@ -220,7 +223,6 @@ public class SendProcessor extends AsyncProcessorSupport implements Traceable, E
 
             // replace message body with variable
             if (variableSend != null) {
-                // it may be a global variable
                 Object value = ExchangeHelper.getVariable(exchange, variableSend);
                 exchange.getMessage().setBody(value);
             }
@@ -232,11 +234,11 @@ 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
+                        // result should be stored in variable instead of message body/headers
                         if (variableReceive != null) {
-                            Object value = exchange.getMessage().getBody();
-                            ExchangeHelper.setVariable(exchange, variableReceive, value);
+                            ExchangeHelper.setVariableFromMessageBodyAndHeaders(exchange, variableReceive);
                             exchange.getMessage().setBody(originalBody);
+                            exchange.getMessage().setHeaders(originalHeaders);
                         }
                         // signal we are done
                         cb.done(doneSync);
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 d55f4e1ce57..a3eb8c98162 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
@@ -1102,6 +1102,31 @@ public final class ExchangeHelper {
         }
     }
 
+    public static void setVariableFromMessageBodyAndHeaders(Exchange exchange, String name) {
+        VariableRepository repo = null;
+        String id = StringHelper.before(name, ":");
+        if (id != null) {
+            VariableRepositoryFactory factory
+                    = exchange.getContext().getCamelContextExtension().getContextPlugin(VariableRepositoryFactory.class);
+            repo = factory.getVariableRepository(id);
+            if (repo == null) {
+                exchange.setException(
+                        new IllegalArgumentException("VariableRepository with id: " + id + " does not exist"));
+            }
+            name = StringHelper.after(name, ":");
+        }
+        Object body = exchange.getMessage().getBody();
+        // do a defensive copy of the headers
+        Map<String, Object> map = exchange.getContext().getCamelContextExtension().getHeadersMapFactory().newMap(exchange.getMessage().getHeaders());
+        if (repo != null) {
+            repo.setVariable(name, body);
+            repo.setVariable(name + ".headers", map);
+        } else {
+            exchange.setVariable(name, body);
+            exchange.setVariable(name + ".headers", map);
+        }
+    }
+
     /**
      * Gets the variable
      *
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
index f1a299106ef..107180f753c 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/MarshalProcessor.java
@@ -59,7 +59,6 @@ public class MarshalProcessor extends AsyncProcessorSupport implements Traceable
         final Object originalBody = in.getBody();
         Object body = originalBody;
         if (variableSend != null) {
-            // it may be a global variable
             body = ExchangeHelper.getVariable(exchange, variableSend);
         }
 
diff --git a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
index dbfb9e7206c..8b93cabd473 100644
--- a/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
+++ b/core/camel-support/src/main/java/org/apache/camel/support/processor/UnmarshalProcessor.java
@@ -68,7 +68,6 @@ public class UnmarshalProcessor extends AsyncProcessorSupport implements Traceab
             final Object originalBody = in.getBody();
             Object body = originalBody;
             if (variableSend != null) {
-                // it may be a global variable
                 body = ExchangeHelper.getVariable(exchange, variableSend);
             }
             final Message out;


(camel) 11/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 6108ed401c1f2ff84fda9bf9d079e8770b8ca767
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Jan 30 10:50:52 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 .../camel/converter/stream/CachedOutputStream.java | 38 ++++++++++++++++++++--
 1 file changed, 35 insertions(+), 3 deletions(-)

diff --git a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java
index 865f0f2a240..49a8c9d7f0e 100644
--- a/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java
+++ b/core/camel-support/src/main/java/org/apache/camel/converter/stream/CachedOutputStream.java
@@ -25,6 +25,7 @@ import org.apache.camel.Exchange;
 import org.apache.camel.StreamCache;
 import org.apache.camel.converter.stream.FileInputStreamCache.TempFileManager;
 import org.apache.camel.spi.StreamCachingStrategy;
+import org.apache.camel.util.IOHelper;
 
 /**
  * This output stream will store the content into a File if the stream context size is exceed the THRESHOLD value. The
@@ -165,9 +166,10 @@ public class CachedOutputStream extends OutputStream {
     }
 
     // This class will close the CachedOutputStream when it is closed
-    private static class WrappedInputStream extends InputStream {
+    private static class WrappedInputStream extends InputStream implements StreamCache {
         private final CachedOutputStream cachedOutputStream;
         private final InputStream inputStream;
+        private long pos;
 
         WrappedInputStream(CachedOutputStream cos, InputStream is) {
             cachedOutputStream = cos;
@@ -176,6 +178,7 @@ public class CachedOutputStream extends OutputStream {
 
         @Override
         public int read() throws IOException {
+            pos++;
             return inputStream.read();
         }
 
@@ -185,8 +188,37 @@ public class CachedOutputStream extends OutputStream {
         }
 
         @Override
-        public synchronized void reset() throws IOException {
-            inputStream.reset();
+        public synchronized void reset() {
+            try {
+                inputStream.reset();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+
+        @Override
+        public void writeTo(OutputStream os) throws IOException {
+            IOHelper.copy(this, os);
+        }
+
+        @Override
+        public StreamCache copy(Exchange exchange) throws IOException {
+            return cachedOutputStream.newStreamCache();
+        }
+
+        @Override
+        public boolean inMemory() {
+            return cachedOutputStream.inMemory;
+        }
+
+        @Override
+        public long length() {
+            return cachedOutputStream.totalLength;
+        }
+
+        @Override
+        public long position() {
+            return pos;
         }
 
         @Override


(camel) 15/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit ad77b1719fa553538adff34d8c9f20c22307ae3f
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Jan 31 09:28:24 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 docs/user-manual/modules/ROOT/pages/variables.adoc | 236 ++-------------------
 1 file changed, 15 insertions(+), 221 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc b/docs/user-manual/modules/ROOT/pages/variables.adoc
index 23b7d92ad00..fca98bbd115 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -214,17 +214,19 @@ The EIPs works in two modes where *variableSend* and *variableReceive* is a litt
 
 |===
 | *VariableSend*       | *VariableReceive*
-| *Headers:* Message   | *Headers:* Variable
-| *Body:* Variable     | *Body:* Variable
+| *Sending Headers:* Message   | *Received Headers:* Variable
+| *Sending Body:* Variable     | *Received Body:* Variable
 |===
 
-The *VariableSend* is intended for sending as regular Camel where the headers are from the current `Message` and the body is
-the variable. In other words it's only the message body that is taken from the variable instead of the current `Message` body.
+The *VariableSend* is intended for sending as regular Camel where the sending headers are from the current `Message` and the body is
+from the variable. In other words it's only the message body that is taken from the variable instead of the current `Message` body.
 
 The *VariableReceive* works in a different mode. The idea is that all the received data is stored as variables. This means the current `Message`
-is not changed at all. The body is stored in the variable, and the received headers (transport headers etc.) are stored as read-only
+is not changed at all. The received body is stored in the variable, and the received headers (transport headers etc.) are stored as read-only
 headers as variables as well. The names of the variable is `header:variableName.headerName`. For example if the variable is `myVar` and the header is `Content-Type`
-then the header is stored as `header:myVar.Content-Type`.
+then the header is stored as a variable with the full name `header:myVar.Content-Type`.
+
+=== Example using VariableReceive
 
 When the EIP is using *VariableReceive*, then the `Message` on the `Exchange` is not in use, but the body and headers will be from the variable.
 For example given the following `Message` containing:
@@ -280,7 +282,8 @@ header.level=gold
 body=Bye World
 ----
 
-However, if you use a variable (_myVar_) as the _sink_ to store the returned data from calling the remote service as shown:
+However, if you use *VariableReceive=myVar* to store the returned data from calling the remote service, into a variable, then
+the dynamics changes as follows:
 
 [tabs]
 ====
@@ -316,7 +319,7 @@ from:
 ----
 ====
 
-Then the `Message` is not changed:
+Then the `Message` on the current `Exchange` is not changed:
 
 [source,properties]
 ----
@@ -330,11 +333,12 @@ And the variable contains all the data received from the remote HTTP service sep
 [source,properties]
 ----
 myVar=Bye World
-myVar.header.level=gold
+header:myVar.level=gold
 ----
 
-IMPORTANT: Notice the headers are stored with the syntax `variable.header.key`. In the example above the variable name is `myVar`,
-and the header key is `level`, which gives: `myVar.header.level`.
+IMPORTANT: Notice the headers are stored with the syntax `header:variable.key`. In the example above the variable name is `myVar`,
+and the header key is `level`, which gives: `header:myVar.level`.
+
 
 === Using variable to store incoming message body
 
@@ -392,213 +396,3 @@ from:
 NOTE: In the examples above the transform `Bye $\{body}` will result as `Bye ` because the `Message` has no message body, as the incoming
 message body is stored in the variable `myKey` instead.
 
-=== Using variables when sending and receiving messages to an endpoint
-
-You can configure the `to` to use variables for any of the following (or both) when sending and receiving:
-
-- variableSend - name of variable that contains the message body to send instead of the current message body on the `Exchange`.
-- variableReceive - name of variable that should store the returned message payload (will not change the message body on the `Exchange`).
-
-For example, you can use the `variableSend` to tell Camel to use the variable as the message body when sending to an endpoint.
-If the `variableReceive` is also configured, then the reply message will be stored in the variable instead of the `Exchange` message body.
-
-IMPORTANT: This is only the message body. Message headers keep acting as usual.
-
-In the following example, we use a variable named `hello` as the message body when sending to the `direct:foo` endpoint:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:send")
-    .setVariable("hello", simple("Camel"))
-    .to("mock:before")
-    .toV("direct:foo", "hello", null)
-    .to("mock:result");
-
-from("direct:foo")
-    .transform().simple("Bye ${body}");
-----
-XML::
-+
-[source,xml]
-----
-<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:foo"/>
-  <transform>
-    <simple>Bye ${body}</simple>
-  </transform>
-</route>
-----
-YAML::
-+
-[source,yaml]
-----
-- 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:foo
-      steps:
-        - transform:
-            simple:
-              expression: "Bye ${body}"
-----
-====
-
-If you only want to store the result in a variable instead of the current `Exchange` message body, then you should use `variableReceive`
-as shown in the following:
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-from("direct:receive")
-    .toV("direct:foo", null, "bye")
-    .to("mock:after")
-    .setBody(simple("${variable:bye}"))
-    .to("mock:result");
-
-from("direct:foo")
-    .transform().simple("Bye ${body}");
-----
-XML::
-+
-[source,xml]
-----
-<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:foo"/>
-  <transform>
-    <simple>Bye ${body}</simple>
-  </transform>
-</route>
-----
-YAML::
-+
-[source,yaml]
-----
-- route:
-    from:
-      uri: direct:receive
-      steps:
-        - to:
-            uri: direct:foo
-            variableReceive: bye
-        - to:
-            uri: mock:after
-        - setBody:
-            variable: bye
-        - to:
-            uri: mock:result
-- route:
-    from:
-      uri: direct:foo
-      steps:
-        - transform:
-            simple:
-              expression: "Bye ${body}"
-----
-====
-
-And you can also use both of them together which means you are using variables for both what to send, and to store the result in a variable.
-This means the current `Exchange` message body is not in use at all.
-
-[tabs]
-====
-Java::
-+
-[source,java]
-----
-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}");
-----
-XML::
-+
-[source,xml]
-----
-<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>
-----
-YAML::
-+
-[source,yaml]
-----
-- 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}"
-----
-====


(camel) 05/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables

Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 8fff1ac7ff754f4a9a91c022b234043d55c1db04
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon Jan 29 11:28:03 2024 +0100

    CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
---
 docs/user-manual/modules/ROOT/pages/variables.adoc | 36 +++++++++++++++-------
 1 file changed, 25 insertions(+), 11 deletions(-)

diff --git a/docs/user-manual/modules/ROOT/pages/variables.adoc b/docs/user-manual/modules/ROOT/pages/variables.adoc
index 31bcc331faa..f52dbcc9687 100644
--- a/docs/user-manual/modules/ROOT/pages/variables.adoc
+++ b/docs/user-manual/modules/ROOT/pages/variables.adoc
@@ -173,7 +173,7 @@ camel.variable.user-template = resource:file:/etc/user.json
 == Using Variables with EIPs
 
 The following commonly used EIPs for sending and receiving, and transforming messages, have
-first class support for using variables with the message body:
+special support for choosing to use variables over the current `Exchange`:
 
 - from
 - to
@@ -185,19 +185,32 @@ first class support for using variables with the message body:
 - marshal
 
 The intention is to make it more convenient and easy to _gather data_ from other systems without any ceremony to keep
-existing data by using techniques such as storing the data temporary using headers or exchange properties,
+existing data by using techniques such as storing the data temporary using headers, exchange properties,
 or with the xref:components:eips:claimCheck-eip.adoc[Claim Check] EIP.
 
 === Important concept when using variables and EIPs
 
-It is **important** to understand that the variables only use the message **body** and does not have support for anything else such
-as message headers. This is on purpose to keep it simpler and only work with the message body as the user data. If you have need
-to use variables with both message body and headers, then you can use `setVariable` and `getVariable`.
+It is **important** to understand that the variables focuses the use of the message **body** only, and makes using **headers** optional.
+This is on purpose to keep it simpler and primary work with the message body as the user data.
+
+The following table summarises what the EIP supports with variables:
+
+|===
+|*EIP* | *VariableSend* | *VariableReceive*
+| From | | yes
+| To | yes | yes
+| ToD | yes | yes
+| Enrich | yes | yes
+| PollEnrich | | yes
+| WireTap | yes |
+| Unmarshal | yes | yes
+| Marshal | yes | yes
+|===
 
 The EIPs listed above have support for using variables when sending and receiving data. This is done by using the `variableSend` and `variableReceive` options
-to specify the name of the variable. When the EIP uses variables then the _data_ itself (i.e. message body) is only what is
-different from 'standard' Camel.
+to specify the name of the variable.
 
+When the EIP is using variables, then the `Message` on the `Exchange` is not in use, but the body and headers will be from the variable.
 For example given the following `Message` containing:
 
 [source,properties]
@@ -207,7 +220,7 @@ header.bar=456
 body=Hello World
 ----
 
-And a remote service is called via the route below, and this service returns a new header and body:
+And a remote service is called via the route below, and this service returns a new header (`level`) and body:
 
 [tabs]
 ====
@@ -287,23 +300,24 @@ from:
 ----
 ====
 
-Then the `Message` body is not changed, but everything else is changed as without using variables:
+Then the `Message` is not changed:
 
 [source,properties]
 ----
 header.foo=123
 header.bar=456
-header.level=gold
 body=Hello World
 ----
 
-And the variable contains the data:
+And the variable contains all the data received from the remote HTTP service separated into two variables:
 
 [source,properties]
 ----
 myVar=Bye World
+myVar.headers.level=gold
 ----
 
+
 === Using variable to store a copy of the incoming message body
 
 You can configure the `from` to store a copy of the message body into a variable. This makes it easy to have quick access