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 <[email protected]>
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);
+ }
+}