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

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

commit abff636874b3b89f7bc14d494daa8233632b23c9
Author: Claus Ibsen <[email protected]>
AuthorDate: Fri Nov 24 09:18:37 2023 +0100

    CAMEL-18768: Add jq function to simple language to make it easy to grab 
some data from JSon message body and use simple for basic JSon transformation
---
 components/camel-jq/src/main/docs/jq-language.adoc | 43 +++++++++++++++-
 .../org/apache/camel/language/jq/JqExpression.java |  3 ++
 .../org/apache/camel/language/jq/JqFunctions.java  | 27 ++++++++++
 .../camel/language/jq/JqSimpleTransformTest.java   | 57 ++++++++++++++++++++++
 .../modules/languages/pages/simple-language.adoc   |  3 ++
 .../simple/ast/SimpleFunctionExpression.java       | 20 ++++++++
 6 files changed, 152 insertions(+), 1 deletion(-)

diff --git a/components/camel-jq/src/main/docs/jq-language.adoc 
b/components/camel-jq/src/main/docs/jq-language.adoc
index ea0f33a0633..5f876cfaad8 100644
--- a/components/camel-jq/src/main/docs/jq-language.adoc
+++ b/components/camel-jq/src/main/docs/jq-language.adoc
@@ -11,7 +11,7 @@
 
 *Since Camel {since}*
 
-Camel supports https://stedolan.github.io/jq[JQ] to allow using 
xref:manual::expression.adoc[Expression] or 
xref:manual::predicate.adoc[Predicate] on JSON messages.
+Camel supports https://jqlang.github.io/jq/[JQ] to allow using 
xref:manual::expression.adoc[Expression] or 
xref:manual::predicate.adoc[Predicate] on JSON messages.
 
 == JQ Options
 
@@ -57,10 +57,13 @@ from("direct:start")
 
 == Camel supplied JQ Functions
 
+NOTE: JQ comes with about a hundred built-in functions, and you can see many 
examples from https://jqlang.github.io/jq/[JQ] documentation.
+
 The camel-jq adds the following functions:
 
 * `header` - Allow to access the Message header in a JQ expression.
 * `property` - Allow to access the Exchange property in a JQ expression.
+* `constant` - Allow to use a constant value as-is in a JQ expression.
 
 For example, to set the property foo with the value from the Message header 
`MyHeader':
 
@@ -82,6 +85,44 @@ from("direct:start")
     .to("mock:result");
 ----
 
+And using a constant value
+
+[source, java]
+----
+from("direct:start")
+    .transform()
+        .jq(".foo = constant(\"Hello World\")")
+    .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
+Camel simple and JQ language as:
+
+[source]
+----
+{
+  "company": "${jq(.customer.name)}",
+  "location": "${jq(.customer.address.country)}",
+  "gold": ${jq(.customer.orders[] | length > 5)}
+}
+----
+
+Here we use the simple language to define the structure and use JQ as inlined 
functions via the `${jq(exp)}` syntax.
+
+This makes it possible to use simple as a template language to define a basic 
structure and then JQ to grab the data
+from an incoming JSon message. The output of the transformation is also JSon, 
but with simple you could
+also make it XML or plain text based:
+
+[source,xml]
+----
+<customer gold="${jq(.customer.orders[] | length > 5)}">
+    <company>${jq(.customer.name)}</company>
+    <location>${jq(.customer.address.country)}</location>
+</customer>
+----
+
 == Dependencies
 
 If you use Maven you could just add the following to your `pom.xml`, 
substituting the version number for the latest and greatest release (see the 
download page for the latest versions).
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 b11611330c6..06689ca9bfd 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
@@ -36,6 +36,7 @@ import org.apache.camel.TypeConverter;
 import org.apache.camel.spi.ExpressionResultTypeAware;
 import org.apache.camel.support.CamelContextHelper;
 import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.MessageHelper;
 
 public class JqExpression extends ExpressionAdapter implements 
ExpressionResultTypeAware {
 
@@ -217,6 +218,8 @@ public class JqExpression extends ExpressionAdapter 
implements ExpressionResultT
             if (payload == null) {
                 throw new InvalidPayloadException(exchange, JsonNode.class);
             }
+            // if body is stream cached then reset, so we can re-read it again
+            MessageHelper.resetStreamCache(exchange.getMessage());
         } else {
             if (headerName != null) {
                 payload = exchange.getMessage().getHeader(headerName, 
JsonNode.class);
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 114d0312a84..82d5ce182fa 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
@@ -30,6 +30,7 @@ import net.thisptr.jackson.jq.Scope;
 import net.thisptr.jackson.jq.Version;
 import net.thisptr.jackson.jq.Versions;
 import net.thisptr.jackson.jq.exception.JsonQueryException;
+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;
@@ -84,6 +85,8 @@ public final class JqFunctions {
         scope.addFunction(Header.NAME, 2, new Header());
         scope.addFunction(Property.NAME, 1, new Property());
         scope.addFunction(Property.NAME, 2, new Property());
+        scope.addFunction(Constant.NAME, 1, new Constant());
+        scope.addFunction(Constant.NAME, 2, new Constant());
     }
 
     public abstract static class ExchangeAwareFunction implements Function {
@@ -227,4 +230,28 @@ public final class JqFunctions {
             }
         }
     }
+
+    /**
+     * A function that returns a constant value as part of JQ expression 
evaluation.
+     *
+     * As example, the following JQ expression sets the {@code .name} property 
to the constant value Donald.
+     *
+     * <pre>
+     * {@code
+     * .name = constant(\"Donald\")"
+     * }
+     * </pre>
+     *
+     */
+    public static class Constant implements Function {
+        public static final String NAME = "constant";
+
+        @Override
+        public void apply(Scope scope, List<Expression> args, JsonNode in, 
Path path, PathOutput output, Version version)
+                throws JsonQueryException {
+            FunctionCall fc = (FunctionCall) args.get(0);
+            String t = fc.toString();
+            output.emit(new TextNode(t), null);
+        }
+    }
 }
diff --git 
a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformTest.java
 
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformTest.java
new file mode 100644
index 00000000000..46c94c7c7d2
--- /dev/null
+++ 
b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqSimpleTransformTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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 JqSimpleTransformTest extends JqTestSupport {
+
+    private static String EXPECTED = """
+            {
+              "roll": 123,
+              "country": "sweden",
+              "fullname": "scott"
+            }""";
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            @Override
+            public void configure() {
+                from("direct:start")
+                        .transform().simple("""
+                                {
+                                  "roll": ${jq(.id)},
+                                  "country": "${jq(.country // 
constant('sweden'))}",
+                                  "fullname": "${jq(.name)}"
+                                }""")
+                        .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 b4825370a24..4325100dd0f 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
@@ -252,6 +252,9 @@ If no type is given the default is used. It is also 
possible to use a custom `Uu
 and bind the bean to the xref:manual::registry.adoc[Registry] with an id. For 
example `${uuid(myGenerator}`
 where the ID is _myGenerator_.
 
+|jq(exp) | Object | When working with JSon data, then this allows to use the 
xref:languages::jq-language.adoc[JQ] language
+for example to extract data from the message body (in JSon format). This 
requires having camel-jq JAR on the classpath.
+
 |=======================================================================
 
 == 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 f6b659fb725..edf10b84a0c 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
@@ -88,6 +88,11 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         if (answer != null) {
             return answer;
         }
+        // custom languages
+        answer = createSimpleCustomLanguage(function, strict);
+        if (answer != null) {
+            return answer;
+        }
 
         // camelContext OGNL
         String remainder = ifStartsWithReturnRemainder("camelContext", 
function);
@@ -446,6 +451,21 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         return null;
     }
 
+    private Expression createSimpleCustomLanguage(String function, boolean 
strict) {
+        // jq
+        String remainder = ifStartsWithReturnRemainder("jq(", function);
+        if (remainder != null) {
+            String exp = StringHelper.beforeLast(remainder, ")");
+            if (exp == null) {
+                throw new SimpleParserException("Valid syntax: ${jq(exp)} was: 
" + function, token.getIndex());
+            }
+            exp = StringHelper.removeQuotes(exp);
+            return ExpressionBuilder.languageExpression("jq", exp);
+        }
+
+        return null;
+    }
+
     private Expression createSimpleExpressionDirectly(CamelContext 
camelContext, String expression) {
         if (ObjectHelper.isEqualToAny(expression, "body", "in.body")) {
             return ExpressionBuilder.bodyExpression();

Reply via email to