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 af42f8088895 CAMEL-24708: camel-groovy - a message script variable,
the Camel 4 name of request (#26478)
af42f8088895 is described below
commit af42f8088895a983b8936a15868442e14e75abc9
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Sep 15 22:52:30 2026 +0200
CAMEL-24708: camel-groovy - a message script variable, the Camel 4 name of
request (#26478)
* CAMEL-24708: camel-groovy - a message script variable, the Camel 4 name
of request, and a missing property hint that only names variables the binding
has
Every other script language (javascript, python, python3, joor) binds
message; groovy only had the template-era request and in. The
MissingPropertyException hint of CAMEL-24698 listed message although it
did not exist, so a script that followed the hint failed again. The hint
now comes from a constant checked against the binding's variable set,
and the groovy card of camel_catalog_doc kind=api says message with
request and in as its aliases.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* CAMEL-24708: camel-jbang - the script variables javadoc names a
difference that still holds now groovy has message too
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
.../apache/camel/catalog/docs/groovy-language.adoc | 3 ++-
.../src/main/docs/groovy-language.adoc | 3 ++-
.../camel/language/groovy/GroovyExpression.java | 24 +++++++++++++++-------
.../groovy/GroovyExpressionBindingTest.java | 15 ++++++++++++--
.../groovy/GroovyMissingPropertyHintTest.java | 8 ++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_23.adoc | 5 +++++
.../dsl/jbang/core/commands/ai/CatalogDocs.java | 16 +++++++--------
.../jbang/core/commands/ai/CatalogDocsTest.java | 9 ++++----
8 files changed, 60 insertions(+), 23 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
index cbbaf920ace3..6dc429ec1ee3 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/groovy-language.adoc
@@ -53,7 +53,8 @@ a `Map`). The `Exchange` is transferred as:
|`camelContext` |The Camel Context.
|`exception` | If the exchange failed then this is the caused exception.
-|`request` |The message.
+|`message` |The message (`exchange.getMessage()`).
+|`request` |The message (the older name of `message`).
|`response` | *Deprecated* The Out message (only for InOut message exchange
pattern).
|`attachments` | A `Map<String,jakarta.activation.DataHandler>` containing
file attachments such as from HTTP file uploads, or emails containing files.
diff --git a/components/camel-groovy/src/main/docs/groovy-language.adoc
b/components/camel-groovy/src/main/docs/groovy-language.adoc
index cbbaf920ace3..6dc429ec1ee3 100644
--- a/components/camel-groovy/src/main/docs/groovy-language.adoc
+++ b/components/camel-groovy/src/main/docs/groovy-language.adoc
@@ -53,7 +53,8 @@ a `Map`). The `Exchange` is transferred as:
|`camelContext` |The Camel Context.
|`exception` | If the exchange failed then this is the caused exception.
-|`request` |The message.
+|`message` |The message (`exchange.getMessage()`).
+|`request` |The message (the older name of `message`).
|`response` | *Deprecated* The Out message (only for InOut message exchange
pattern).
|`attachments` | A `Map<String,jakarta.activation.DataHandler>` containing
file attachments such as from HTTP file uploads, or emails containing files.
diff --git
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyExpression.java
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyExpression.java
index ee22fb79d927..0bde520fe6a1 100644
---
a/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyExpression.java
+++
b/components/camel-groovy/src/main/java/org/apache/camel/language/groovy/GroovyExpression.java
@@ -44,6 +44,15 @@ public class GroovyExpression extends ExpressionSupport {
private static final Logger LOG =
LoggerFactory.getLogger(GroovyExpression.class);
+ /**
+ * The script variables named in the missing property hint: the ones a
script reaches for, without the aliases
+ * (header, variable, in, exchangeProperty) and the out message that only
exists on an InOut exchange. Every name
+ * must be in {@link ExchangeBinding#EXCHANGE_VARIABLES}.
+ */
+ static final String SCRIPT_VARIABLES_HINT
+ = "exchange, message, body, headers, variables,
exchangeProperties, exception, camelContext, attachments"
+ + " and log";
+
private final String text;
// the language and shell factory of the CamelContext, resolved once
instead of on every evaluation
@@ -90,8 +99,7 @@ public class GroovyExpression extends ExpressionSupport {
? "'" + name + "' is a bean in the registry, not a script
variable; use"
+ " exchange.getContext().getRegistry().lookupByName('"
+ name + "'), or call it from the route with"
+ " - bean: {ref: " + name + "}"
- : "the script variables are exchange, message, body,
headers, variables, exchangeProperties,"
- + " camelContext, request and log";
+ : "the script variables are " + SCRIPT_VARIABLES_HINT;
throw new groovy.lang.MissingPropertyException(
e.getMessageWithoutLocationText() + " (" + hint + ")",
name, e.getType());
}
@@ -180,7 +188,7 @@ public class GroovyExpression extends ExpressionSupport {
/**
* Binding with the same variables as {@link
ExchangeHelper#populateVariableMap(Exchange, Map, boolean)} plus
- * attachments and log.
+ * message (the current message, the Camel 4 name of request), attachments
and log.
* <p>
* The body, the headers, the exception and the out message are read when
the binding is created. The values that
* are costly to create (the copy of the exchange properties, the variable
repository and the attachment message)
@@ -191,11 +199,12 @@ public class GroovyExpression extends ExpressionSupport {
* A global variable of the {@link GroovyShellFactory} is hidden by the
exchange variable with the same name, except
* {@code out} and {@code response} when the exchange has no out message,
as they are then not exposed.
*/
- private static final class ExchangeBinding extends Binding {
+ static final class ExchangeBinding extends Binding {
- private static final Set<String> EXCHANGE_VARIABLES = Set.of(
- "body", "header", "headers", "variable", "variables",
"exception", "in", "request", "exchange",
- "exchangeProperty", "exchangeProperties", "out", "response",
"camelContext", "attachments", "log");
+ static final Set<String> EXCHANGE_VARIABLES = Set.of(
+ "body", "header", "headers", "variable", "variables",
"exception", "in", "request", "message",
+ "exchange", "exchangeProperty", "exchangeProperties", "out",
"response", "camelContext", "attachments",
+ "log");
private final Exchange exchange;
private final Message in;
@@ -297,6 +306,7 @@ public class GroovyExpression extends ExpressionSupport {
return exception;
case "in":
case "request":
+ case "message":
return in;
case "exchange":
return exchange;
diff --git
a/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyExpressionBindingTest.java
b/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyExpressionBindingTest.java
index 50f3ced73ed5..bdd84e7d18e4 100644
---
a/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyExpressionBindingTest.java
+++
b/components/camel-groovy/src/test/java/org/apache/camel/language/groovy/GroovyExpressionBindingTest.java
@@ -77,6 +77,8 @@ public class GroovyExpressionBindingTest {
assertEquals("myValue", evaluate("exchangeProperties.myProperty"));
assertSame(exchange, evaluate("exchange"));
assertSame(exchange.getIn(), evaluate("request"));
+ assertSame(exchange.getMessage(), evaluate("message"));
+ assertEquals("James", evaluate("message.getHeader('name')"));
assertSame(context, evaluate("camelContext"));
assertEquals(Boolean.TRUE, evaluate("attachments.isEmpty()"));
assertEquals(Boolean.TRUE, evaluate("log != null"));
@@ -103,12 +105,21 @@ public class GroovyExpressionBindingTest {
assertEquals("World", exchange.getIn().getBody());
}
+ @Test
+ public void testEveryVariableTheHintNamesExists() {
+ // the hint is hand-written; a name it lists must be one the binding
exposes, so the next attempt succeeds
+ for (String name : GroovyExpression.SCRIPT_VARIABLES_HINT.replace("
and ", ", ").split(", ")) {
+
assertTrue(GroovyExpression.ExchangeBinding.EXCHANGE_VARIABLES.contains(name),
+ "the hint names '" + name + "' but the binding does not
expose it");
+ }
+ }
+
@Test
public void testBindingVariables() {
assertEquals(Boolean.TRUE, evaluate(
"binding.variables.keySet().containsAll(['body', 'header',
'headers', 'variable', 'variables', 'exception',"
- + " 'in', 'request', 'exchange',
'exchangeProperty', 'exchangeProperties',"
- + " 'camelContext', 'attachments',
'log'])"));
+ + " 'in', 'request', 'message',
'exchange', 'exchangeProperty',"
+ + " 'exchangeProperties',
'camelContext', 'attachments', 'log'])"));
assertEquals("World", evaluate("binding.variables.body"));
assertEquals("Bye", evaluate("body = 'Bye'; binding.variables.body"));
}
diff --git
a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyMissingPropertyHintTest.java
b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyMissingPropertyHintTest.java
index 59b3dc6d6c4e..dd9f76b72f85 100644
---
a/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyMissingPropertyHintTest.java
+++
b/components/camel-groovy/src/test/java/org/apache/camel/processor/groovy/GroovyMissingPropertyHintTest.java
@@ -22,6 +22,7 @@ import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.test.junit6.CamelTestSupport;
import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -50,6 +51,12 @@ public class GroovyMissingPropertyHintTest extends
CamelTestSupport {
cause.getMessage());
}
+ @Test
+ public void messageIsAScriptVariableAsTheHintSays() {
+ String out = template.requestBodyAndHeader("direct:message", "World",
"name", "Hello", String.class);
+ assertEquals("Hello World", out);
+ }
+
@Override
protected RoutesBuilder createRouteBuilder() {
return new RouteBuilder() {
@@ -57,6 +64,7 @@ public class GroovyMissingPropertyHintTest extends
CamelTestSupport {
public void configure() {
from("direct:bean").transform().groovy("formatter.append(body)");
from("direct:unknown").transform().groovy("nosuch.toUpperCase()");
+
from("direct:message").transform().groovy("message.getHeader('name') + ' ' +
message.body");
}
};
}
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
index 6d14e5e81d19..939d694c7c92 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_23.adoc
@@ -116,6 +116,11 @@ from the exchange the first time the script uses them
instead of being copied be
changes a property or variable through `exchange` and then reads one of these
variables for the first time now sees
its own change where it previously saw the state from before the script ran.
+Groovy scripts now have a `message` variable for the current message
(`exchange.getMessage()`), the name the other
+script languages and the Camel 4 API use; `request` and `in` stay as its older
names. A `GroovyShellFactory` that
+provided its own global variable named `message` is now hidden by the exchange
variable, like the other exchange
+variable names.
+
=== camel-dynamic-router
The `dynamic-router-control` endpoint no longer takes the subscription
`predicate`, or the
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
index 866210e7eaeb..32b315d6973c 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocs.java
@@ -469,8 +469,9 @@ public final class CatalogDocs {
/**
* The variables a script language binds, hand-written because each
language binds its own set with its own names
- * (groovy has camelContext and no message, javascript has context and
message), and how a script reaches the Camel
- * API and a registry bean from them; null for a language that is not a
script.
+ * (the CamelContext is camelContext in groovy and context in javascript,
the exchange properties exchangeProperties
+ * and properties), and how a script reaches the Camel API and a registry
bean from them; null for a language that
+ * is not a script.
*/
static JsonObject scriptVariables(String language) {
JsonObject variables = new JsonObject();
@@ -478,7 +479,8 @@ public final class CatalogDocs {
switch (language) {
case "groovy" -> {
variables.put("exchange", "the Exchange");
- variables.put("request", "the message (exchange.getMessage());
in is an alias");
+ variables.put("message",
+ "the message (exchange.getMessage()); request is an
alias, in is an alias (older names)");
variables.put("body", "the message body");
variables.put("headers", "the message headers (Map); header is
an alias");
variables.put("variables", "the exchange variables (Map);
variable is an alias");
@@ -488,12 +490,10 @@ public final class CatalogDocs {
variables.put("attachments", "the message attachments (Map)");
variables.put("log", "an SLF4J logger");
variables.put("response", "the out message, only when one
exists; out is an alias");
- note = "There is no message variable: use request (or
exchange.message). The value of the last"
- + " statement is the result. A registry bean is not a
variable: use"
+ note = "The value of the last statement is the result. A
registry bean is not a variable: use"
+ " camelContext.registry.lookupByName('myBean').
Setting body or headers in the script"
- + " does not change the message: use
exchange.message.body = ... or exchange.message"
- + ".setHeader(name, value). Groovy property syntax
works on the Camel API: exchange.message.body,"
- + " exchange.context.registry.";
+ + " does not change the message: use message.body = ...
or message.setHeader(name, value)."
+ + " Groovy property syntax works on the Camel API:
message.body, exchange.context.registry.";
}
case "js", "python" -> {
variables.put("exchange", "the Exchange");
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
index 521fc8f9ba3f..adfb3680cba3 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/CatalogDocsTest.java
@@ -276,8 +276,8 @@ class CatalogDocsTest {
JsonObject groovy = catalogDoc(Map.of("name", "groovy", "kind",
"api"));
assertEquals("api", groovy.getString("kind"));
JsonObject vars = groovy.getMap("variables");
- assertTrue(vars.containsKey("camelContext") &&
vars.containsKey("request") && vars.containsKey("log"));
- assertFalse(vars.containsKey("message"), "groovy binds no message
variable");
+ assertTrue(vars.containsKey("camelContext") &&
vars.containsKey("message") && vars.containsKey("log"));
+ assertTrue(vars.getString("message").contains("request"), "the older
name is given as an alias");
assertTrue(groovy.getString("note").contains("lookupByName('myBean')"), "a bean
name is not a variable");
assertTrue(groovy.getCollection("apis").contains("Exchange"));
@@ -294,7 +294,7 @@ class CatalogDocsTest {
JsonObject lang = catalogDoc(Map.of("name", "groovy", "kind",
"language"));
assertEquals("language", lang.getString("kind"));
assertTrue(lang.getMap("scriptVariables").containsKey("camelContext"));
- assertTrue(lang.getString("scriptNote").contains("no message
variable"));
+
assertTrue(lang.getString("scriptNote").contains("lookupByName('myBean')"));
assertNull(catalogDoc(Map.of("name", "simple", "kind",
"language")).get("scriptVariables"));
}
@@ -316,7 +316,8 @@ class CatalogDocsTest {
}
}
JsonObject groovy =
CatalogDocs.scriptVariables("groovy").getMap("variables");
- assertTrue(groovy.containsKey("attachments") &&
groovy.containsKey("log"), "the groovy extras");
+ assertTrue(groovy.containsKey("message") &&
groovy.containsKey("attachments") && groovy.containsKey("log"),
+ "the groovy extras");
}
}
}