allthingssecurity opened a new pull request, #27525:
URL: https://github.com/apache/camel/pull/27525

   # Description
   
   [CAMEL-24467](https://issues.apache.org/jira/browse/CAMEL-24467)
   
   Groovy and Jactl bind Exchange variables as `variable` and `variables`, the 
names `ExchangeHelper.populateVariableMap` uses (camel-quickjs binds 
`variables` as well). The other scripting languages did not bind them. In 
JavaScript, MVEL, OGNL and Jython a script could only reach them through 
`exchange.getVariable(...)`. In python3's default data-only mode there was no 
way to read them at all, because `exchange` is not bound there.
   
   This change implements the scope I proposed on the ticket on 2026-09-25 (and 
the correction posted the same day). It is additive: no existing binding or 
method is renamed or removed.
   
   - **js** (`JavaScriptExpression`): binds `variable` and `variables` to 
`exchange.getVariables()`, next to `headers` and `properties`. 
`JavaScriptExpression` is the only place the language binds Exchange data. 
`JavaScriptLanguage.evaluate(script, bindings, type)` (the `ScriptingLanguage` 
entry point) takes its bindings from the caller, so it is unchanged.
   - **python** (Jython, `PythonExpression`): the same, `variable` and 
`variables`.
   - **mvel** and **ognl** (`RootObject`): add `getVariables()`, 
`getVariable(String)` and `getVariable(String, Class<T>)`, mirroring 
`getHeaders()`/`getHeader(...)`. In both languages a root-object method is 
called by its full name: `getHeader('foo')` works, but `header('foo')` does not 
resolve. So the variable getter is `getVariable('foo')`, the same form that 
works for headers. The existing docs tables of both languages listed 
`header(name)`, `header(name, type)`, `property(name)` and `property(name, 
type)`, which never resolved; this PR corrects them to 
`getHeader(...)`/`getProperty(...)`.
   
     Correction to my earlier comment on CAMEL-24467: I wrote that 
`variable('foo')` would work in MVEL/OGNL like `header('foo')`. Neither does; 
the working forms are `getVariable('foo')` and `getHeader('foo')`. 
`getVariable` goes through `Exchange.getVariable`, so it also resolves 
`global:` and `route:` variables. The MVEL *component* (templates) already had 
variables through `ExchangeHelper.createVariableMap`.
   - **spel** (camel-spring `RootObject`): the same three getters as MVEL and 
OGNL, so `#{variables.foo}`, `#{variables['foo']}`, `#{getVariable('foo')}` and 
`#{getVariable('num', T(Integer))}` work. Its docs table had the same 
`header(name)`/`property(name)` rows, which fail (`EL1004E: Method call: Method 
header(java.lang.String) cannot be found on type 
org.apache.camel.language.spel.RootObject`); they now read 
`getHeader(...)`/`getProperty(...)` as well. These getters are not SpEL's 
`#name` evaluation-context variables, which Camel does not set.
   - **python3** (GraalPy): binds `variables` as a data binding in exactly the 
way `headers` is bound. It is bound in both the default and the trusted 
(`createWithHostAccess()`) mode, because `headers` is bound in both. Host 
access is not widened. In default mode, Python can index the map but cannot 
call Java methods on it (`variables.put(...)` and `variables.getClass()` raise 
`AttributeError`). Only `variables` is added, with no `variable` alias, 
matching python3's existing names (`headers`, no `header`) and camel-quickjs, 
the other sandboxed language. In default mode, global and route variables 
cannot be read (there is no `exchange`), and an assignment through the map does 
not go through `Exchange.setVariable`, so `variables['global:x'] = 1` creates 
an exchange variable literally named `global:x`; the docs say both. Like 
`headers`, it is the exchange's own map rather than a copy, so 
`variables['foo'] = 'bar'` in a script updates the exchange. One difference 
from `headers`: the vari
 ables map is a `ConcurrentHashMap`, so `variables['foo'] = None` fails with a 
`NullPointerException`. The docs mention it.
   
   The `variables` map only contains exchange-scoped variables. 
`global:`/`route:` variables are reachable through `getVariable` in MVEL/OGNL 
and through `exchange.getVariable(...)` in js, python and trusted python3, and 
the docs say so. The language metadata JSON (`js.json`, `mvel.json`, ...) does 
not list bound variables, so it needs no change.
   
   Examples:
   
   ```java
   // JavaScript
   .setBody().js("variables.greeting + ' ' + variable.get('name')")
   .filter().js("variables.name == 'Camel'")
   
   // Jython
   .filter().python("variables['name'] == 'Camel'")
   
   // MVEL / OGNL
   .filter().mvel("variables.name == 'Camel'")
   .setHeader("n").mvel("getVariable('num', Integer) + 1")
   .setHeader("n").ognl("getVariable('num', @java.lang.Integer@class) + 1")
   .setHeader("g").ognl("getVariable('global:greeting')")
   
   // Python 3 (default, data-only mode)
   .setBody().python3("f\"{variables['greeting']} {variables['name']}\"")
   .filter().python3("variables['name'] == 'Camel'")
   ```
   
   Cost: `exchange.getVariables()` creates the exchange's variable store, so 
js, python and python3 bind the variables only when the script text names them 
(`variable` or `variables`; `variables` for python3), checked once when the 
expression is created. A script that does not use variables creates no store 
and costs the same as before (Groovy and Jactl also create the store only on 
use). A script that reaches the binding through a computed name (such as 
`globals()['vari' + 'ables']`) does not see it; `exchange.getVariables()` still 
works in js, python and trusted python3. The root-object getters of MVEL, OGNL 
and SpEL only run when an expression uses them.
   
   A shared definition of the bound names, and a common facade for them across 
languages, are left for a follow-up. Federico Mariani suggested this on 
CAMEL-24467 (2026-09-11, from the review of the camel-quickjs `camel` facade in 
CAMEL-24688 / #26309): one place in camel-support that lists the binding names 
and the facade operations (`getBody/setBody`, `get/set/removeHeader`, 
`get/set/removeProperty`, `get/set/removeVariable`, `log`), so that jactl and 
python3 could offer mutation without trusted mode and every scripting 
language's docs could point to the same table. This PR only covers the bindings 
side.
   
   Tests (one new package-private JUnit 5 class per component, using the 
module's assertion style):
   - `JavaScriptVariablesTest`, `PythonVariablesTest`: `variables`/`variable` 
in expressions and predicates, and a route that sets variables with the Set 
Variable EIP and reads them in `setBody` and a `choice`.
   - `MvelVariablesTest`, `OgnlVariablesTest`: `variables.foo`, 
`variables['foo']`, `getVariable('foo')`, the typed getter (a `"123"` variable 
read as an `Integer`, so `+ 1` gives `124` and not `"1231"`), 
`getVariable('global:...')`, and the same route.
   - `Python3VariablesTest`: default mode reads the map, writes propagate like 
`headers`, Java methods on the map are denied, trusted mode also binds 
`variables`, and the same route.
   - `SpelVariablesTest` (camel-spring-xml, where the SpEL tests live): 
`#{variables.foo}`, `#{variables['foo']}`, `#{getVariable('foo')}`, the typed 
getter and `getVariable('global:...')`.
   - `variableStoreIsOnlyCreatedForScriptsThatNameTheVariables` in the js, 
python and python3 classes: through an `Exchange` proxy that counts 
`getVariables()` calls, a script without variables makes none and a script with 
them makes one. These three pass on main (where nothing is bound); they fail 
with the earlier version of this change that bound the variables up front (`a 
script without variables should not create the variable store ==> expected: <0> 
but was: <1>`).
   
   Rebased on `main` (374c04877418, 2026-10-05). `main` has not changed the 
binding code of these five languages since 2026-09-25 (the only commit touching 
them is the docs-tabs change CAMEL-25265 in the python3 page, which rebased 
cleanly).
   
   Without the main-code change, every new test fails (with surefire's reruns, 
re-checked after the rebase):
   ```
   JavaScriptVariablesTest.variablesAreBound » Polyglot ReferenceError: 
variables is not defined
   MvelVariablesTest.variablesAreBound » ExpressionEvaluation [Error: could not 
access: variables; in class: org.apache.camel.language.mvel.RootObject]
   MvelVariablesTest.typedVariableGetterConverts » ExpressionEvaluation [Error: 
unable to resolve method: 
org.apache.camel.language.mvel.RootObject.getVariable(java.lang.String, 
java.lang.Class) ...]
   OgnlVariablesTest.variablesAreBound » ExpressionEvaluation 
ognl.NoSuchPropertyException: 
org.apache.camel.language.ognl.RootObject.variables
   OgnlVariablesTest.typedVariableGetterConverts » ExpressionEvaluation 
ognl.MethodFailedException: Method "getVariable" failed for object 
org.apache.camel.language.ognl.RootObject@...
   PythonVariablesTest.variablesAreBound » ExpressionIllegalSyntax Illegal 
syntax: variables['foo']
   Python3VariablesTest.defaultModeBindsVariablesAsData » ExpressionEvaluation 
NameError: name 'variables' is not defined
   Python3VariablesTest.defaultModeDeniesJavaMethodsOnVariables 
[variables.put('k', 'v') should mention "AttributeError" but was: ... 
NameError: name 'variables' is not defined]
   ```
   js 2/2, mvel 4/4, ognl 4/4, python 2/2 and python3 5/5 tests failed. With 
main's SpEL `RootObject`, the 3 `SpelVariablesTest` tests fail too (`EL1008E: 
Property or field 'variables' cannot be found on object of type 
'org.apache.camel.language.spel.RootObject'`, `EL1004E: Method call: Method 
getVariable(java.lang.String) cannot be found ...`). The route tests fail with 
a `CamelExecutionException` from the same errors.
   
   With the change, the full test suites pass: camel-javascript 17, camel-mvel 
17, camel-ognl 16, camel-python 12, camel-python3 49 and camel-spring-xml 1171 
(26 skipped) tests, 0 failures, 0 errors (camel-spring built in the same 
reactor).
   
   The language docs list the new bindings, and the mirrored copies under 
`catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/` 
were regenerated with `prepare-catalog` (they are byte-identical copies). 
Rebuilding the changed modules left no uncommitted changes.
   
   Not changed, per the ticket: camel-groovy, camel-jactl and camel-quickjs 
already bind variables, and camel-joor exposes `exchange`, so 
`exchange.getVariable(...)` works there. There is no camel-jexl module on 
`main`. Follow-up, not changed: the MVEL, OGNL and SpEL tables also say 
`*this*` is the Exchange, but the root object is the `RootObject` wrapper 
(`#{#root}` in SpEL is a `RootObject`); `exchange` is the Exchange.
   
   # Target
   
   - [x] I checked that the commit is targeting the correct branch (Camel 4 
uses the `main` branch)
   
   # Tracking
   - [x] If this is a large change, bug fix, or code improvement, I checked 
there is a [JIRA issue](https://issues.apache.org/jira/browse/CAMEL) filed for 
the change (usually before you start working on it).
   
   # Apache Camel coding standards and style
   
   - [x] I checked that each commit in the pull request has a meaningful 
subject line and body.
   - [ ] I have run `mvn clean install -DskipTests` locally from root folder 
and I have committed all auto-generated changes.
     (I built and tested the affected modules, including the formatter and 
import-sort plugins, and regenerated the catalog docs. I did not run the full 
root build.)
   
   # AI-assisted contributions
   
   - [x] If this PR includes AI-generated code, commits have proper 
co-authorship attribution (e.g., `Co-authored-by` trailers) and the PR 
description identifies the AI tool used.
     This PR was prepared with Claude Code (Claude Opus 5.5). The commit 
carries a `Co-Authored-By` trailer.
   
   _Claude Code on behalf of allthingssecurity_
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to