You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by lb...@apache.org on 2022/06/17 13:08:56 UTC

[camel] branch main updated: camel-jq: add an option to retrieve the payload to process from an exchange property

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

lburgazzoli 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 9f0b55d3c54 camel-jq: add an option to retrieve the payload to process from an exchange property
9f0b55d3c54 is described below

commit 9f0b55d3c54816e7d13126c33ae2b5f8571d9c63
Author: Luca Burgazzoli <lb...@gmail.com>
AuthorDate: Fri Jun 17 14:06:17 2022 +0200

    camel-jq: add an option to retrieve the payload to process from an exchange property
---
 .../resources/org/apache/camel/language/jq/jq.json |  2 +-
 .../org/apache/camel/language/jq/JqExpression.java | 65 ++++++++++++++++-----
 .../org/apache/camel/language/jq/JqLanguage.java   | 11 ++++
 ...a => JqExpressionFromHeaderOrPropertyTest.java} | 48 ++++++++++-----
 .../language/jq/JqExpressionFromHeaderTest.java    | 38 ++++++++----
 ...Test.java => JqExpressionFromPropertyTest.java} | 18 +++---
 .../language/jq/JqExpressionHeaderFnTest.java      |  8 +--
 .../language/jq/JqExpressionPropertyFnTest.java    |  6 +-
 .../camel/language/jq/JqExpressionSimpleTest.java  |  7 +--
 .../apache/camel/language/jq/JqTestSupport.java    | 15 +++++
 .../camel/NoSuchHeaderOrPropertyException.java     | 68 ++++++++++++++++++++++
 .../org/apache/camel/model/language/jq.json        |  2 +-
 .../org/apache/camel/builder/ExpressionClause.java | 47 +++++++++++----
 .../camel/builder/ExpressionClauseSupport.java     | 52 ++++++++++++++---
 .../apache/camel/model/language/JqExpression.java  | 17 ++++++
 .../reifier/language/JqExpressionReifier.java      |  3 +-
 .../java/org/apache/camel/xml/in/ModelParser.java  |  8 ++-
 17 files changed, 328 insertions(+), 87 deletions(-)

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 3051e61bbd6..a6d6bab62f2 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,7 +18,7 @@
   "properties": {
     "expression": { "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" },
     "resultType": { "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)" },
-    "headerName": { "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" },
+    "headerName": { "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." },
     "trim": { "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" },
     "id": { "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" }
   }
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 97777d72724..7512312ec85 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
@@ -30,7 +30,7 @@ import net.thisptr.jackson.jq.exception.JsonQueryException;
 import org.apache.camel.CamelContext;
 import org.apache.camel.Exchange;
 import org.apache.camel.InvalidPayloadException;
-import org.apache.camel.NoSuchHeaderException;
+import org.apache.camel.NoSuchHeaderOrPropertyException;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.TypeConverter;
 import org.apache.camel.spi.ExpressionResultTypeAware;
@@ -44,10 +44,12 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
     private Scope scope;
     private String resultTypeName;
     private Class<?> resultType;
-    private String headerName;
     private JsonQuery query;
     private TypeConverter typeConverter;
 
+    private String headerName;
+    private String propertyName;
+
     public JqExpression(String expression) {
         this(null, expression);
     }
@@ -135,11 +137,26 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
 
     /**
      * Name of header to use as input, instead of the message body
+     * </p>
+     * It has as higher precedent than the propertyName if both are set.
      */
     public void setHeaderName(String headerName) {
         this.headerName = headerName;
     }
 
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    /**
+     * Name of property to use as input, instead of the message body.
+     * </p>
+     * It has a lower precedent than the headerName if both are set.
+     */
+    public void setPropertyName(String propertyName) {
+        this.propertyName = propertyName;
+    }
+
     @Override
     public boolean matches(Exchange exchange) {
         final Object value = evaluate(exchange, Object.class);
@@ -164,19 +181,7 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
             JqFunctions.EXCHANGE_LOCAL.set(exchange);
 
             final List<JsonNode> outputs = new ArrayList<>(1);
-            final JsonNode payload;
-
-            if (headerName == null) {
-                payload = exchange.getMessage().getBody(JsonNode.class);
-                if (payload == null) {
-                    throw new InvalidPayloadException(exchange, JsonNode.class);
-                }
-            } else {
-                payload = exchange.getMessage().getHeader(headerName, JsonNode.class);
-                if (payload == null) {
-                    throw new NoSuchHeaderException(exchange, headerName, JsonNode.class);
-                }
-            }
+            final JsonNode payload = getPayload(exchange);
 
             this.query.apply(scope, payload, outputs::add);
 
@@ -205,4 +210,34 @@ public class JqExpression extends ExpressionAdapter implements ExpressionResultT
 
         return null;
     }
+
+    /**
+     * Determines the payload by looking at heders, properties and finally the payload.
+     *
+     * @param  exchange  the {@link Exchange} being processed
+     * @return           the {@link JsonNode} to be processed by the expression
+     * @throws Exception
+     */
+    private JsonNode getPayload(Exchange exchange) throws Exception {
+        JsonNode payload = null;
+
+        if (headerName == null && propertyName == null) {
+            payload = exchange.getMessage().getBody(JsonNode.class);
+            if (payload == null) {
+                throw new InvalidPayloadException(exchange, JsonNode.class);
+            }
+        } else {
+            if (headerName != null) {
+                payload = exchange.getMessage().getHeader(headerName, JsonNode.class);
+            }
+            if (payload == null && propertyName != null) {
+                payload = exchange.getProperty(propertyName, JsonNode.class);
+            }
+            if (payload == null) {
+                throw new NoSuchHeaderOrPropertyException(exchange, headerName, propertyName, JsonNode.class);
+            }
+        }
+
+        return payload;
+    }
 }
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 30ad2c326e1..4803da6ad4a 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
@@ -28,6 +28,7 @@ public class JqLanguage extends LanguageSupport implements StaticService {
 
     private Class<?> resultType;
     private String headerName;
+    private String propertyName;
 
     public Class<?> getResultType() {
         return resultType;
@@ -45,6 +46,14 @@ public class JqLanguage extends LanguageSupport implements StaticService {
         this.headerName = headerName;
     }
 
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    public void setPropertyName(String propertyName) {
+        this.propertyName = propertyName;
+    }
+
     @Override
     public void start() {
         // noop
@@ -70,6 +79,7 @@ public class JqLanguage extends LanguageSupport implements StaticService {
         JqExpression answer = new JqExpression(expression);
         answer.setResultType(resultType);
         answer.setHeaderName(headerName);
+        answer.setPropertyName(propertyName);
         answer.init(getCamelContext());
         return answer;
     }
@@ -79,6 +89,7 @@ public class JqLanguage extends LanguageSupport implements StaticService {
         JqExpression answer = new JqExpression(expression);
         answer.setResultType(property(Class.class, properties, 0, resultType));
         answer.setHeaderName(property(String.class, properties, 1, headerName));
+        answer.setPropertyName(property(String.class, properties, 2, propertyName));
         answer.init(getCamelContext());
         return answer;
     }
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderOrPropertyTest.java
similarity index 56%
copy from components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
copy to components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderOrPropertyTest.java
index 718d0e3bee5..3c43371840f 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderOrPropertyTest.java
@@ -16,13 +16,12 @@
  */
 package org.apache.camel.language.jq;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
-import org.apache.camel.NoSuchHeaderException;
+import org.apache.camel.NoSuchHeaderOrPropertyException;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
-public class JqExpressionFromHeaderTest extends JqTestSupport {
+public class JqExpressionFromHeaderOrPropertyTest extends JqTestSupport {
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
@@ -30,9 +29,9 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
             public void configure() {
                 from("direct:start")
                         .doTry()
-                        .transform().jq(".foo", "Content")
+                        .transform().jq(".foo", "Content", "ContentProp")
                         .to("mock:result")
-                        .doCatch(NoSuchHeaderException.class)
+                        .doCatch(NoSuchHeaderOrPropertyException.class)
                         .to("mock:fail");
 
             }
@@ -40,31 +39,52 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
     }
 
     @Test
-    public void testExpressionFromHeader() throws Exception {
+    public void testExpressionFromProperty() throws Exception {
         getMockEndpoint("mock:result")
                 .expectedBodiesReceived(new TextNode("bar"));
         getMockEndpoint("mock:fail")
                 .expectedMessageCount(0);
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> {
+                    e.getMessage().setHeader("Invalid", node("foo", "baz"));
+                    e.setProperty("ContentProp", node("foo", "bar"));
+                })
+                .send();
 
-        template.sendBodyAndHeader("direct:start", null, "Content", node);
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testExpressionFromHeader() throws Exception {
+        getMockEndpoint("mock:result")
+                .expectedBodiesReceived(new TextNode("baz"));
+        getMockEndpoint("mock:fail")
+                .expectedMessageCount(0);
+
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> {
+                    e.getMessage().setHeader("Content", node("foo", "baz"));
+                    e.setProperty("ContentProp", node("foo", "bar"));
+                })
+                .send();
 
         assertMockEndpointsSatisfied();
     }
 
     @Test
-    public void testExpressionFromHeaderFail() throws Exception {
+    public void testExpressionFail() throws Exception {
         getMockEndpoint("mock:result")
                 .expectedMessageCount(0);
         getMockEndpoint("mock:fail")
                 .expectedMessageCount(1);
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
-
-        template.sendBody("direct:start", node);
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> {
+                    e.getMessage().setHeader("Invalid", node("foo", "baz"));
+                    e.setProperty("Invalid", node("foo", "bar"));
+                })
+                .send();
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
index 718d0e3bee5..2737c907118 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
@@ -16,9 +16,8 @@
  */
 package org.apache.camel.language.jq;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
-import org.apache.camel.NoSuchHeaderException;
+import org.apache.camel.NoSuchHeaderOrPropertyException;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
@@ -32,7 +31,7 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
                         .doTry()
                         .transform().jq(".foo", "Content")
                         .to("mock:result")
-                        .doCatch(NoSuchHeaderException.class)
+                        .doCatch(NoSuchHeaderOrPropertyException.class)
                         .to("mock:fail");
 
             }
@@ -46,10 +45,28 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
         getMockEndpoint("mock:fail")
                 .expectedMessageCount(0);
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> {
+                    e.getMessage().setHeader("Content", node("foo", "bar"));
+                })
+                .send();
 
-        template.sendBodyAndHeader("direct:start", null, "Content", node);
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testExpressionFromHeaderPriority() throws Exception {
+        getMockEndpoint("mock:result")
+                .expectedBodiesReceived(new TextNode("bar"));
+        getMockEndpoint("mock:fail")
+                .expectedMessageCount(0);
+
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> {
+                    e.getMessage().setHeader("Content", node("foo", "bar"));
+                    e.setProperty("Content", node("foo", "baz"));
+                })
+                .send();
 
         assertMockEndpointsSatisfied();
     }
@@ -61,10 +78,11 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
         getMockEndpoint("mock:fail")
                 .expectedMessageCount(1);
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
-
-        template.sendBody("direct:start", node);
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> {
+                    e.getMessage().setBody(node("foo", "bar"));
+                })
+                .send();
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromPropertyTest.java
similarity index 77%
copy from components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
copy to components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromPropertyTest.java
index 718d0e3bee5..4e7aa9ce969 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromHeaderTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionFromPropertyTest.java
@@ -18,11 +18,11 @@ package org.apache.camel.language.jq;
 
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
-import org.apache.camel.NoSuchHeaderException;
+import org.apache.camel.NoSuchHeaderOrPropertyException;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
-public class JqExpressionFromHeaderTest extends JqTestSupport {
+public class JqExpressionFromPropertyTest extends JqTestSupport {
     @Override
     protected RouteBuilder createRouteBuilder() {
         return new RouteBuilder() {
@@ -32,7 +32,7 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
                         .doTry()
                         .transform().jq(".foo", "Content")
                         .to("mock:result")
-                        .doCatch(NoSuchHeaderException.class)
+                        .doCatch(NoSuchHeaderOrPropertyException.class)
                         .to("mock:fail");
 
             }
@@ -40,7 +40,7 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
     }
 
     @Test
-    public void testExpressionFromHeader() throws Exception {
+    public void testExpressionFromProperty() throws Exception {
         getMockEndpoint("mock:result")
                 .expectedBodiesReceived(new TextNode("bar"));
         getMockEndpoint("mock:fail")
@@ -49,13 +49,15 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
         ObjectNode node = MAPPER.createObjectNode();
         node.put("foo", "bar");
 
-        template.sendBodyAndHeader("direct:start", null, "Content", node);
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> e.setProperty("Content", node))
+                .send();
 
         assertMockEndpointsSatisfied();
     }
 
     @Test
-    public void testExpressionFromHeaderFail() throws Exception {
+    public void testExpressionFromPropertyFail() throws Exception {
         getMockEndpoint("mock:result")
                 .expectedMessageCount(0);
         getMockEndpoint("mock:fail")
@@ -64,7 +66,9 @@ public class JqExpressionFromHeaderTest extends JqTestSupport {
         ObjectNode node = MAPPER.createObjectNode();
         node.put("foo", "bar");
 
-        template.sendBody("direct:start", node);
+        fluentTemplate.to("direct:start")
+                .withProcessor(e -> e.getMessage().setBody(node))
+                .send();
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionHeaderFnTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionHeaderFnTest.java
index f45e3075323..f726bc0cf80 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionHeaderFnTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionHeaderFnTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.language.jq;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
@@ -36,12 +35,9 @@ public class JqExpressionHeaderFnTest extends JqTestSupport {
     @Test
     public void testExpression() throws Exception {
         getMockEndpoint("mock:result")
-                .expectedBodiesReceived(MAPPER.createObjectNode().put("foo", "MyValue"));
+                .expectedBodiesReceived(node("foo", "MyValue"));
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
-
-        template.sendBodyAndHeader("direct:start", node, "MyHeader", "MyValue");
+        template.sendBodyAndHeader("direct:start", node("foo", "bar"), "MyHeader", "MyValue");
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionPropertyFnTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionPropertyFnTest.java
index eeccf381c34..443a7dc0c76 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionPropertyFnTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionPropertyFnTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.language.jq;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
 
@@ -38,13 +37,10 @@ public class JqExpressionPropertyFnTest extends JqTestSupport {
         getMockEndpoint("mock:result")
                 .expectedBodiesReceived(MAPPER.createObjectNode().put("foo", "MyPropertyValue"));
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
-
         fluentTemplate.to("direct:start")
                 .withProcessor(e -> {
                     e.setProperty("MyProperty", "MyPropertyValue");
-                    e.getMessage().setBody(node);
+                    e.getMessage().setBody(node("foo", "bar"));
                 })
                 .send();
 
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionSimpleTest.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionSimpleTest.java
index 5757c8042bc..e112ea043c1 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionSimpleTest.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqExpressionSimpleTest.java
@@ -16,7 +16,6 @@
  */
 package org.apache.camel.language.jq;
 
-import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.node.TextNode;
 import org.apache.camel.builder.RouteBuilder;
 import org.junit.jupiter.api.Test;
@@ -38,11 +37,7 @@ public class JqExpressionSimpleTest extends JqTestSupport {
     public void testExpression() throws Exception {
         getMockEndpoint("mock:result").expectedBodiesReceived(new TextNode("bar"));
 
-        ObjectNode node = MAPPER.createObjectNode();
-        node.put("foo", "bar");
-        node.put("baz", "bak");
-
-        template.sendBody("direct:start", node);
+        template.sendBody("direct:start", node("foo", "bar", "baz", "bak"));
 
         assertMockEndpointsSatisfied();
     }
diff --git a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqTestSupport.java b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqTestSupport.java
index a0416bde7d5..d0c5566d5cf 100644
--- a/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqTestSupport.java
+++ b/components/camel-jq/src/test/java/org/apache/camel/language/jq/JqTestSupport.java
@@ -17,8 +17,23 @@
 package org.apache.camel.language.jq;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.node.ObjectNode;
 import org.apache.camel.test.junit5.CamelTestSupport;
 
 public abstract class JqTestSupport extends CamelTestSupport {
     public static final ObjectMapper MAPPER = new ObjectMapper();
+
+    public static ObjectNode node(String key, String value, String... keyVals) {
+        ObjectNode answer = MAPPER.createObjectNode();
+
+        answer.put(key, value);
+
+        for (int i = 0; i < keyVals.length; i += 2) {
+            answer.put(
+                    keyVals[i],
+                    keyVals[i + 1]);
+        }
+
+        return answer;
+    }
 }
diff --git a/core/camel-api/src/main/java/org/apache/camel/NoSuchHeaderOrPropertyException.java b/core/camel-api/src/main/java/org/apache/camel/NoSuchHeaderOrPropertyException.java
new file mode 100644
index 00000000000..8e8d4221d4a
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/NoSuchHeaderOrPropertyException.java
@@ -0,0 +1,68 @@
+/*
+ * 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;
+
+public class NoSuchHeaderOrPropertyException extends CamelExchangeException {
+
+    private final String headerName;
+    private final String propertyName;
+    private final transient Class<?> type;
+
+    public NoSuchHeaderOrPropertyException(Exchange exchange, String headerName, String propertyName, Class<?> type) {
+        super(String.format(
+                "Np '%s' header or '%s' property available of type: %s (header: %s, property: %s)",
+                headerName,
+                propertyName,
+                type.getName(),
+                header(exchange, headerName),
+                property(exchange, headerName)),
+              exchange);
+
+        this.headerName = headerName;
+        this.propertyName = propertyName;
+        this.type = type;
+    }
+
+    public String getHeaderName() {
+        return headerName;
+    }
+
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+
+    protected static String header(Exchange exchange, String headerName) {
+        Object value = exchange.getMessage().getHeader(headerName);
+        return valueDescription(value);
+    }
+
+    protected static String property(Exchange exchange, String propertyName) {
+        Object value = exchange.getProperty(propertyName);
+        return valueDescription(value);
+    }
+
+    static String valueDescription(Object value) {
+        if (value == null) {
+            return "null";
+        }
+        return "has value: " + value + " of type: " + value.getClass().getCanonicalName();
+    }
+}
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 5f7ff9a0664..4d69c75867a 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,7 +15,7 @@
   "properties": {
     "expression": { "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" },
     "resultType": { "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)" },
-    "headerName": { "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" },
+    "headerName": { "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." },
     "trim": { "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" },
     "id": { "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" }
   }
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
index 440f7b4d71d..d785fe9a27c 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/builder/ExpressionClause.java
@@ -402,24 +402,49 @@ public class ExpressionClause<T> implements Expression, Predicate {
     /**
      * Evaluates a <a href="http://camel.apache.org/jq.html">JQ expression</a>
      *
-     * @param  value      the expression to be evaluated
-     * @param  headerName the name of the header to apply the expression to
-     * @return            the builder to continue processing the DSL
+     * @param  value                the expression to be evaluated
+     * @param  headerOrPropertyName the name of the header or property to apply the expression to
+     * @return                      the builder to continue processing the DSL
      */
-    public T jq(String value, String headerName) {
-        return delegate.jq(value, headerName);
+    public T jq(String value, String headerOrPropertyName) {
+        return delegate.jq(value, headerOrPropertyName);
     }
 
     /**
      * Evaluates a <a href="http://camel.apache.org/jq.html">JQ expression</a>
      *
-     * @param  value      the expression to be evaluated
-     * @param  resultType the return type expected by the expression
-     * @param  headerName the name of the header to apply the expression to
-     * @return            the builder to continue processing the DSL
+     * @param  value        the expression to be evaluated
+     * @param  headerName   the name of the header to apply the expression to
+     * @param  propertyName the name of the property to apply the expression to
+     * @return              the builder to continue processing the DSL
+     */
+    public T jq(String value, String headerName, String propertyName) {
+        return delegate.jq(value, headerName, propertyName);
+    }
+
+    /**
+     * Evaluates a <a href="http://camel.apache.org/jq.html">JQ expression</a>
+     *
+     * @param  value                the expression to be evaluated
+     * @param  resultType           the return type expected by the expression
+     * @param  headerOrPropertyName the name of the header or property to apply the expression to
+     * @return                      the builder to continue processing the DSL
+     */
+    public T jq(String value, Class<?> resultType, String headerOrPropertyName) {
+        return delegate.jq(value, resultType, headerOrPropertyName);
+    }
+
+    /**
+     * Evaluates a <a href="http://camel.apache.org/jq.html">JQ expression</a>
+     *
+     * @param  value        the expression to be evaluated
+     * @param  resultType   the return type expected by the expression
+     * @param  headerName   the name of the header to apply the expression to
+     * @param  propertyName the name of the property to apply the expression to
+     * @return              the builder to continue processing the DSL
      */
-    public T jq(String value, Class<?> resultType, String headerName) {
-        return delegate.jq(value, resultType, headerName);
+    public T jq(String value, Class<?> resultType, String headerName, String propertyName) {
+        return delegate.jq(value, resultType, headerName, propertyName);
     }
 
     /**
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 ca3b05b3c36..fba2d270aa6 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
@@ -398,28 +398,62 @@ public class ExpressionClauseSupport<T> implements ExpressionFactoryAware, Predi
     /**
      * Evaluates <a href="http://camel.apache.org/jq.html">JQ expression</a>
      *
-     * @param  text       the expression to be evaluated
-     * @param  headerName the name of the header to apply the expression to
-     * @return            the builder to continue processing the DSL
+     * @param  text                 the expression to be evaluated
+     * @param  headerOrPropertyName the name of the header or the property to apply the expression to
+     * @return                      the builder to continue processing the DSL
+     */
+    public T jq(String text, String headerOrPropertyName) {
+        JqExpression exp = new JqExpression(text);
+        exp.setHeaderName(headerOrPropertyName);
+        exp.setPropertyName(headerOrPropertyName);
+        return expression(exp);
+    }
+
+    /**
+     * Evaluates <a href="http://camel.apache.org/jq.html">JQ expression</a>
+     *
+     * @param  text         the expression to be evaluated
+     * @param  headerName   the name of the header to apply the expression to
+     * @param  propertyName the name of the propertyName to apply the expression to
+     * @return              the builder to continue processing the DSL
      */
-    public T jq(String text, String headerName) {
+    public T jq(String text, String headerName, String propertyName) {
         JqExpression exp = new JqExpression(text);
         exp.setHeaderName(headerName);
+        exp.setPropertyName(propertyName);
         return expression(exp);
     }
 
     /**
      * Evaluates <a href="http://camel.apache.org/jq.html">JQ expression</a>
      *
-     * @param  text       the expression to be evaluated
-     * @param  resultType the return type expected by the expression
-     * @param  headerName the name of the header to apply the expression to
-     * @return            the builder to continue processing the DSL
+     * @param  text                 the expression to be evaluated
+     * @param  resultType           the return type expected by the expression
+     * @param  headerOrPropertyName the name of the header or the property to apply the expression to
+     * @return                      the builder to continue processing the DSL
+     */
+    public T jq(String text, Class<?> resultType, String headerOrPropertyName) {
+        JqExpression exp = new JqExpression(text);
+        exp.setResultType(resultType);
+        exp.setHeaderName(headerOrPropertyName);
+        exp.setPropertyName(headerOrPropertyName);
+        return expression(exp);
+    }
+
+    /**
+     * Evaluates <a href="http://camel.apache.org/jq.html">JQ expression</a>
+     *
+     * @param  text         the expression to be evaluated
+     * @param  resultType   the return type expected by the expression
+     * @param  headerName   the name of the header to apply the expression to
+     * @param  propertyName the name of the propertyName to apply the expression to
+     * @return              the builder to continue processing the DSL
      */
-    public T jq(String text, Class<?> resultType, String headerName) {
+    public T jq(String text, Class<?> resultType, String headerName, String propertyName) {
         JqExpression exp = new JqExpression(text);
         exp.setResultType(resultType);
         exp.setHeaderName(headerName);
+        exp.setPropertyName(propertyName);
         return expression(exp);
     }
 
diff --git a/core/camel-core-model/src/main/java/org/apache/camel/model/language/JqExpression.java b/core/camel-core-model/src/main/java/org/apache/camel/model/language/JqExpression.java
index 1dc1bd91943..ee8a1b95bcf 100644
--- a/core/camel-core-model/src/main/java/org/apache/camel/model/language/JqExpression.java
+++ b/core/camel-core-model/src/main/java/org/apache/camel/model/language/JqExpression.java
@@ -38,6 +38,8 @@ public class JqExpression extends ExpressionDefinition {
     @XmlAttribute
     @Metadata(label = "advanced")
     private String headerName;
+    @Metadata(label = "advanced")
+    private String propertyName;
 
     public JqExpression() {
     }
@@ -79,8 +81,23 @@ public class JqExpression extends ExpressionDefinition {
 
     /**
      * Name of header to use as input, instead of the message body
+     * </p>
+     * It has as higher precedent than the propertyName if both are set.
      */
     public void setHeaderName(String headerName) {
         this.headerName = headerName;
     }
+
+    public String getPropertyName() {
+        return propertyName;
+    }
+
+    /**
+     * Name of property to use as input, instead of the message body.
+     * </p>
+     * It has a lower precedent than the headerName if both are set.
+     */
+    public void setPropertyName(String propertyName) {
+        this.propertyName = propertyName;
+    }
 }
diff --git a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JqExpressionReifier.java b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JqExpressionReifier.java
index d2a4c5a23f2..fe3a5c1a4e4 100644
--- a/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JqExpressionReifier.java
+++ b/core/camel-core-reifier/src/main/java/org/apache/camel/reifier/language/JqExpressionReifier.java
@@ -43,9 +43,10 @@ public class JqExpressionReifier extends ExpressionReifier<JqExpression> {
     }
 
     private Object[] createProperties() {
-        Object[] properties = new Object[2];
+        Object[] properties = new Object[3];
         properties[0] = definition.getResultType();
         properties[1] = parseString(definition.getHeaderName());
+        properties[2] = parseString(definition.getPropertyName());
         return properties;
     }
 
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 4cae265b4f6..4398c2d3f46 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
@@ -2653,7 +2653,13 @@ public class ModelParser extends BaseParser {
                 default: return expressionDefinitionAttributeHandler().accept(def, key, val);
             }
             return true;
-        }, noElementHandler(), expressionDefinitionValueHandler());
+        }, (def, key) -> {
+            if ("propertyName".equals(key)) {
+                def.setPropertyName(doParseText());
+                return true;
+            }
+            return false;
+        }, expressionDefinitionValueHandler());
     }
     protected JsonPathExpression doParseJsonPathExpression() throws IOException, XmlPullParserException {
         return doParse(new JsonPathExpression(), (def, key, val) -> {