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

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


The following commit(s) were added to refs/heads/main by this push:
     new 010dcd71755 CAMEL-21928: camel-jq - include variable() and body() as 
new functions to access in-process data
010dcd71755 is described below

commit 010dcd717550a79fb49d15dc40bb5a2d801dfad1
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Sat Apr 5 09:53:55 2025 +0200

    CAMEL-21928: camel-jq - include variable() and body() as new functions to 
access in-process data
---
 components/camel-jq/src/main/docs/jq-language.adoc | 13 +++
 .../org/apache/camel/language/jq/JqFunctions.java  | 95 ++++++++++++++++++++++
 .../camel/language/jq/JqExpressionBodyFnTest.java  | 74 +++++++++++++++++
 .../language/jq/JqExpressionVariableFnTest.java    | 46 +++++++++++
 4 files changed, 228 insertions(+)

diff --git a/components/camel-jq/src/main/docs/jq-language.adoc 
b/components/camel-jq/src/main/docs/jq-language.adoc
index 82ade292985..f022840bd41 100644
--- a/components/camel-jq/src/main/docs/jq-language.adoc
+++ b/components/camel-jq/src/main/docs/jq-language.adoc
@@ -50,6 +50,8 @@ The camel-jq adds the following functions:
 * `header`: allow accessing the Message header in a JQ expression.
 * `property`: allow accessing the Exchange property in a JQ expression.
 * `constant`: allow using a constant value as-is in a JQ expression.
+* `variable`: allow accessing the Exchange variable in a JQ expression.
+* `body`: the message body as a textual value.
 
 For example, to set the property foo with the value from the Message header 
`MyHeader':
 
@@ -81,6 +83,17 @@ from("direct:start")
     .to("mock:result");
 ----
 
+Or using an exchange variable:
+
+[source, java]
+----
+from("direct:start")
+    .transform()
+        .jq(".foo = variable(\"MyVar\")")
+    .to("mock:result");
+----
+
+
 === Transforming a JSon message
 
 For basic JSon transformation where you have a fixed structure, you can 
represent with a combination of using
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 2c28a795d38..e1b3ec86c19 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
@@ -34,6 +34,7 @@ import net.thisptr.jackson.jq.internal.tree.FunctionCall;
 import net.thisptr.jackson.jq.path.Path;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
+import org.apache.camel.StreamCache;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -88,6 +89,9 @@ public final class JqFunctions {
         scope.addFunction(Property.NAME, 2, new Property());
         scope.addFunction(Constant.NAME, 1, new Constant());
         scope.addFunction(Constant.NAME, 2, new Constant());
+        scope.addFunction(Variable.NAME, 1, new Variable());
+        scope.addFunction(Variable.NAME, 2, new Variable());
+        scope.addFunction(Body.NAME, new Body());
     }
 
     public abstract static class ExchangeAwareFunction implements Function {
@@ -255,4 +259,95 @@ public final class JqFunctions {
             output.emit(new TextNode(t), null);
         }
     }
+
+    /**
+     * A function that allow to retrieve an {@link org.apache.camel.Exchange} 
variable value as part of JQ expression
+     * evaluation.
+     *
+     * As example, the following JQ expression sets the {@code .name} property 
to the value of the variable named
+     * {@code CommitterName}.
+     *
+     * <pre>
+     * {@code
+     * .name = variable(\"CommitterName\")"
+     * }
+     * </pre>
+     *
+     */
+    public static class Variable extends ExchangeAwareFunction {
+        public static final String NAME = "variable";
+
+        @Override
+        protected void doApply(
+                Scope scope,
+                List<Expression> args,
+                JsonNode in,
+                Path path,
+                PathOutput output,
+                Version version,
+                Exchange exchange)
+                throws JsonQueryException {
+
+            args.get(0).apply(scope, in, name -> {
+                if (args.size() == 2) {
+                    args.get(1).apply(scope, in, defval -> {
+                        extract(
+                                exchange,
+                                name.asText(),
+                                defval.asText(),
+                                output);
+                    });
+                } else {
+                    extract(
+                            exchange,
+                            name.asText(),
+                            null,
+                            output);
+                }
+            });
+        }
+
+        private void extract(Exchange exchange, String variableName, String 
variableValue, PathOutput output)
+                throws JsonQueryException {
+            String variable = exchange.getVariable(variableName, 
variableValue, String.class);
+
+            if (variable == null) {
+                output.emit(NullNode.getInstance(), null);
+            } else {
+                output.emit(new TextNode(variable), null);
+            }
+        }
+    }
+
+    /**
+     * A function that returns the message body as part of JQ expression 
evaluation.
+     *
+     * As example, the following JQ expression sets the {@code .name} property 
to the message body.
+     *
+     * <pre>
+     * {@code
+     * .name = body()"
+     * }
+     * </pre>
+     *
+     */
+    public static class Body implements Function {
+        public static final String NAME = "body";
+
+        @Override
+        public void apply(Scope scope, List<Expression> args, JsonNode in, 
Path path, PathOutput output, Version version)
+                throws JsonQueryException {
+            Exchange exchange = EXCHANGE_LOCAL.get();
+            if (exchange != null) {
+                // in case you refer to the body multiple times then we need 
to handle stream-caching
+                Object body = exchange.getMessage().getBody();
+                if (body instanceof StreamCache sc) {
+                    sc.reset();
+                }
+                String t = exchange.getMessage().getBody(String.class);
+                output.emit(new TextNode(t), null);
+            }
+        }
+    }
+
 }
diff --git 
a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionBodyFnTest.java
 
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionBodyFnTest.java
new file mode 100644
index 00000000000..000439234ad
--- /dev/null
+++ 
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionBodyFnTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class JqExpressionBodyFnTest extends JqTestSupport {
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                var jq = expression().jq(". + [{\"array\": 
body()}]").source("mydata").end();
+
+                from("direct:start")
+                        .setVariable("mydata", simple("${body}"))
+                        .setVariable("mydata", jq)
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testExpression() throws Exception {
+        getMockEndpoint("mock:result")
+                .expectedMessageCount(1);
+
+        ObjectMapper mapper = new ObjectMapper();
+        ArrayNode arr = mapper.createArrayNode();
+
+        ObjectNode user1 = mapper.createObjectNode();
+        user1.put("id", 1);
+        user1.put("name", "John Doe");
+        arr.add(user1);
+
+        ObjectNode user2 = mapper.createObjectNode();
+        user2.put("id", 2);
+        user2.put("name", "Jane Jackson");
+        arr.add(user2);
+
+        template.sendBody("direct:start", arr);
+
+        MockEndpoint.assertIsSatisfied(context);
+
+        String res = 
getMockEndpoint("mock:result").getExchanges().get(0).getVariable("mydata", 
String.class);
+        // should copy the body into the array node
+        var n = mapper.reader().readTree(res);
+        var a = n.get(2);
+        Assertions.assertNotNull(a);
+        a = a.get("array");
+        // the body functions return the data as text
+        Assertions.assertTrue(a.isTextual());
+    }
+}
diff --git 
a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionVariableFnTest.java
 
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionVariableFnTest.java
new file mode 100644
index 00000000000..8b1003dbb4a
--- /dev/null
+++ 
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionVariableFnTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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 JqExpressionVariableFnTest extends JqTestSupport {
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        .setVariable("MyVar", constant("MyValue"))
+                        .transform().jq(".foo = variable(\"MyVar\")")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testExpression() throws Exception {
+        getMockEndpoint("mock:result")
+                .expectedBodiesReceived(node("foo", "MyValue"));
+
+        template.sendBody("direct:start", node("foo", "bar"));
+
+        MockEndpoint.assertIsSatisfied(context);
+    }
+}

Reply via email to