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:28 UTC
(camel) 12/16: CAMEL-19749: variables - Should also copy message headers into variable when using EIP variables
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
*/