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

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

commit a1da0560b6104fb2191dc46e2e7a86a252040138
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Dec 28 13:19:45 2023 +0100

    CAMEL-19749: Add variables as concept to Camel
---
 .../camel-bean/src/main/docs/bean-language.adoc    |   2 +-
 .../bean/AbstractCamelInvocationHandler.java       |  14 +++
 .../org/apache/camel/component/bean/BeanInfo.java  |   7 ++
 .../src/main/java/org/apache/camel/Variable.java   |  39 +++++++
 .../src/main/java/org/apache/camel/Variables.java  |  34 ++++++
 .../component/bean/BeanPipelineVariablesTest.java  |  96 +++++++++++++++++
 ...eanWithPropertiesAndVariablesInjectionTest.java |  97 +++++++++++++++++
 .../bean/BeanWithVariablesAndBodyInject3Test.java  |  87 ++++++++++++++++
 .../BeanWithVariablesAndBodyInjectionTest.java     |  99 ++++++++++++++++++
 .../camel/processor/MethodFilterVariableTest.java  |  74 +++++++++++++
 .../camel/support/builder/ExpressionBuilder.java   | 115 +++++++++++++++++++++
 .../ROOT/pages/parameter-binding-annotations.adoc  |   9 +-
 12 files changed, 671 insertions(+), 2 deletions(-)

diff --git a/components/camel-bean/src/main/docs/bean-language.adoc 
b/components/camel-bean/src/main/docs/bean-language.adoc
index 85996a113ab..f7138e4e5ce 100644
--- a/components/camel-bean/src/main/docs/bean-language.adoc
+++ b/components/camel-bean/src/main/docs/bean-language.adoc
@@ -76,7 +76,7 @@ public boolean isGoldCustomer(String body) {...}
 === Using Annotations for bean integration
 
 You can also use the xref:manual::bean-integration.adoc[Bean Integration]
-annotations, such as `@Header`, `@Body` etc
+annotations, such as `@Header`, `@Body`, `@Variable` etc
 
 [source,java]
 ----
diff --git 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/AbstractCamelInvocationHandler.java
 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/AbstractCamelInvocationHandler.java
index b7cc688765e..4cd04ab2b37 100644
--- 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/AbstractCamelInvocationHandler.java
+++ 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/AbstractCamelInvocationHandler.java
@@ -43,6 +43,8 @@ import org.apache.camel.Headers;
 import org.apache.camel.InvalidPayloadException;
 import org.apache.camel.Producer;
 import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.Variable;
+import org.apache.camel.Variables;
 import org.apache.camel.support.DefaultExchange;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.StringHelper;
@@ -109,6 +111,8 @@ public abstract class AbstractCamelInvocationHandler 
implements InvocationHandle
             for (Parameter parameter : method.getParameters()) {
                 if (parameter.isAnnotationPresent(Header.class)
                         || parameter.isAnnotationPresent(Headers.class)
+                        || parameter.isAnnotationPresent(Variable.class)
+                        || parameter.isAnnotationPresent(Variables.class)
                         || 
parameter.isAnnotationPresent(ExchangeProperty.class)
                         || parameter.isAnnotationPresent(Body.class)) {
                     canUseBinding = true;
@@ -139,6 +143,16 @@ public abstract class AbstractCamelInvocationHandler 
implements InvocationHandle
                             if (map != null) {
                                 exchange.getIn().getHeaders().putAll(map);
                             }
+                        } else if 
(ann.annotationType().isAssignableFrom(Variable.class)) {
+                            Variable variable = (Variable) ann;
+                            String name = variable.value();
+                            exchange.setVariable(name, value);
+                        } else if 
(ann.annotationType().isAssignableFrom(Variables.class)) {
+                            Map<String, Object> map
+                                    = 
exchange.getContext().getTypeConverter().tryConvertTo(Map.class, exchange, 
value);
+                            if (map != null) {
+                                exchange.getVariables().putAll(map);
+                            }
                         } else if 
(ann.annotationType().isAssignableFrom(ExchangeProperty.class)) {
                             ExchangeProperty ep = (ExchangeProperty) ann;
                             String name = ep.value();
diff --git 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
index 5e0a27b908f..99b646dcaa3 100644
--- 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
+++ 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
@@ -42,6 +42,8 @@ import org.apache.camel.Header;
 import org.apache.camel.Headers;
 import org.apache.camel.Message;
 import org.apache.camel.PropertyInject;
+import org.apache.camel.Variable;
+import org.apache.camel.Variables;
 import org.apache.camel.support.ObjectHelper;
 import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.support.language.AnnotationExpressionFactory;
@@ -1012,6 +1014,11 @@ public class BeanInfo {
             return 
ExpressionBuilder.headerExpression(headerAnnotation.value());
         } else if (annotation instanceof Headers) {
             return ExpressionBuilder.headersExpression();
+        } else if (annotation instanceof Variable) {
+            Variable variableAnnotation = (Variable) annotation;
+            return 
ExpressionBuilder.variableExpression(variableAnnotation.value());
+        } else if (annotation instanceof Variables) {
+            return ExpressionBuilder.variablesExpression();
         } else if (annotation instanceof ExchangeException) {
             return 
ExpressionBuilder.exchangeExceptionExpression(CastUtils.cast(parameterType, 
Exception.class));
         } else if (annotation instanceof PropertyInject) {
diff --git a/core/camel-api/src/main/java/org/apache/camel/Variable.java 
b/core/camel-api/src/main/java/org/apache/camel/Variable.java
new file mode 100644
index 00000000000..e91a29b749a
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/Variable.java
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a parameter as being a variable
+ *
+ * @see Exchange#getVariable(String)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ ElementType.PARAMETER })
+public @interface Variable {
+
+    /**
+     * Name of variable
+     */
+    String value();
+}
diff --git a/core/camel-api/src/main/java/org/apache/camel/Variables.java 
b/core/camel-api/src/main/java/org/apache/camel/Variables.java
new file mode 100644
index 00000000000..30e9ccfd7f2
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/Variables.java
@@ -0,0 +1,34 @@
+/*
+ * 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;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a parameter as being an injection point of the variables
+ *
+ * @see Exchange#getVariables()
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Target({ ElementType.PARAMETER })
+public @interface Variables {
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanPipelineVariablesTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanPipelineVariablesTest.java
new file mode 100644
index 00000000000..02355fd2da8
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanPipelineVariablesTest.java
@@ -0,0 +1,96 @@
+/*
+ * 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.component.bean;
+
+import java.util.Map;
+
+import org.apache.camel.Body;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Variables;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+/**
+ * Unit test of bean can propagate headers in a pipeline
+ */
+public class BeanPipelineVariablesTest extends ContextTestSupport {
+
+    @Test
+    public void testBeanInPipeline() throws Exception {
+        MockEndpoint mock = getMockEndpoint("mock:result");
+        mock.expectedBodiesReceived("Hello World from James");
+        mock.expectedVariableReceived("from", "James");
+
+        template.sendBodyAndHeader("direct:input", "Hello World", "from", 
"Claus");
+        mock.assertIsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            public void configure() throws Exception {
+                from("direct:input").pipeline("bean:foo", 
"bean:bar?method=usingExchange", "bean:baz").to("mock:result");
+            }
+        };
+    }
+
+    @Override
+    protected Registry createRegistry() throws Exception {
+        Registry answer = super.createRegistry();
+        answer.bind("foo", new FooBean());
+        answer.bind("bar", new BarBean());
+        answer.bind("baz", new BazBean());
+        return answer;
+    }
+
+    public static class FooBean {
+        public void onlyPlainBody(Object body) {
+            assertEquals("Hello World", body);
+        }
+    }
+
+    public static class BarBean {
+        public void doNotUseMe(String body) {
+            fail("Should not invoce me");
+        }
+
+        public void usingExchange(Exchange exchange) {
+            String body = exchange.getIn().getBody(String.class);
+            assertEquals("Hello World", body);
+            assertEquals("Claus", exchange.getIn().getHeader("from"));
+            exchange.setVariable("from", "James");
+            exchange.getMessage().setBody("Hello World from James");
+        }
+    }
+
+    public static class BazBean {
+        public void doNotUseMe(String body) {
+            fail("Should not invoce me");
+        }
+
+        public void withAnnotations(@Variables Map<String, Object> variables, 
@Body String body) {
+            assertEquals("Hello World from James", body);
+            assertEquals("James", variables.get("from"));
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithPropertiesAndVariablesInjectionTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithPropertiesAndVariablesInjectionTest.java
new file mode 100644
index 00000000000..b627ce63ead
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithPropertiesAndVariablesInjectionTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.component.bean;
+
+import java.util.Map;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.ExchangeProperties;
+import org.apache.camel.Message;
+import org.apache.camel.Processor;
+import org.apache.camel.Variables;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.processor.BeanRouteTest;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class BeanWithPropertiesAndVariablesInjectionTest extends 
ContextTestSupport {
+    private static final Logger LOG = 
LoggerFactory.getLogger(BeanRouteTest.class);
+    protected MyBean myBean = new MyBean();
+
+    @Test
+    public void testSendMessage() throws Exception {
+        template.send("direct:in", new Processor() {
+            public void process(Exchange exchange) throws Exception {
+                exchange.setProperty("p1", "abc");
+                exchange.setProperty("p2", 123);
+
+                Message in = exchange.getIn();
+                exchange.setVariable("h1", "xyz");
+                exchange.setVariable("h2", 456);
+            }
+        });
+
+        Map<?, ?> foo = myBean.foo;
+        Map<?, ?> bar = myBean.bar;
+        assertNotNull(foo, "myBean.foo");
+        assertNotNull(bar, "myBean.bar");
+
+        assertEquals("abc", foo.get("p1"), "foo.p1");
+        assertEquals(123, foo.get("p2"), "foo.p2");
+
+        assertEquals("xyz", bar.get("h1"), "bar.h1");
+        assertEquals(456, bar.get("h2"), "bar.h2");
+    }
+
+    @Override
+    protected Registry createRegistry() throws Exception {
+        Registry answer = super.createRegistry();
+        answer.bind("myBean", myBean);
+        return answer;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("direct:in").bean("myBean");
+            }
+        };
+    }
+
+    public static class MyBean {
+        public Map<?, ?> foo;
+        public Map<?, ?> bar;
+
+        @Override
+        public String toString() {
+            return "MyBean[foo: " + foo + " bar: " + bar + "]";
+        }
+
+        public void myMethod(@ExchangeProperties Map<?, ?> foo, @Variables 
Map<?, ?> bar) {
+            this.foo = foo;
+            this.bar = bar;
+            LOG.info("myMethod() method called on {}", this);
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithVariablesAndBodyInject3Test.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithVariablesAndBodyInject3Test.java
new file mode 100644
index 00000000000..795f0ca46bd
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithVariablesAndBodyInject3Test.java
@@ -0,0 +1,87 @@
+/*
+ * 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.component.bean;
+
+import java.util.Map;
+
+import org.apache.camel.Body;
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Variables;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.mock.MockEndpoint;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+public class BeanWithVariablesAndBodyInject3Test extends ContextTestSupport {
+    private final MyBean myBean = new MyBean();
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                
from("direct:start").to("bean:myBean?method=doSomething").to("mock:finish");
+            }
+        };
+    }
+
+    @Test
+    public void testInOnly() throws Exception {
+        MockEndpoint end = getMockEndpoint("mock:finish");
+        end.expectedBodiesReceived("Hello!");
+
+        sendBody("direct:start", "Test Input");
+
+        assertMockEndpointsSatisfied();
+
+        assertNotNull(end.getExchanges().get(0).getIn().getBody());
+        assertEquals("Hello!", end.getExchanges().get(0).getIn().getBody());
+    }
+
+    @Test
+    public void testInOut() throws Exception {
+        MockEndpoint end = getMockEndpoint("mock:finish");
+        end.expectedBodiesReceived("Hello!");
+        end.expectedVariableReceived("out", 123);
+
+        String out = template.requestBody("direct:start", "Test Input", 
String.class);
+        assertEquals("Hello!", out);
+
+        assertMockEndpointsSatisfied();
+
+        assertNotNull(end.getExchanges().get(0).getIn().getBody());
+        assertEquals("Hello!", end.getExchanges().get(0).getIn().getBody());
+        assertEquals(123, end.getExchanges().get(0).getVariable("out"));
+    }
+
+    @Override
+    protected Registry createRegistry() throws Exception {
+        Registry answer = super.createRegistry();
+        answer.bind("myBean", myBean);
+        return answer;
+    }
+
+    public static class MyBean {
+
+        public String doSomething(@Body String body, @Variables Map<String, 
Object> variables) {
+            variables.put("out", 123);
+            return "Hello!";
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithVariablesAndBodyInjectionTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithVariablesAndBodyInjectionTest.java
new file mode 100644
index 00000000000..e1245c0cdeb
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanWithVariablesAndBodyInjectionTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.component.bean;
+
+import java.util.Map;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
+import org.apache.camel.Processor;
+import org.apache.camel.Variables;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.processor.BeanRouteTest;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class BeanWithVariablesAndBodyInjectionTest extends ContextTestSupport {
+    private static final Logger LOG = 
LoggerFactory.getLogger(BeanRouteTest.class);
+    protected MyBean myBean = new MyBean();
+
+    @Test
+    public void testSendMessage() throws Exception {
+        template.send("direct:in", new Processor() {
+            public void process(Exchange exchange) throws Exception {
+                exchange.setProperty("p1", "abc");
+                exchange.setProperty("p2", 123);
+
+                Message in = exchange.getIn();
+                exchange.setVariable("h1", "xyz");
+                exchange.setVariable("h2", 456);
+                in.setBody("TheBody");
+            }
+        });
+
+        Map<String, Object> foo = myBean.variables;
+        assertNotNull(foo, "myBean.foo");
+
+        assertEquals("xyz", foo.get("h1"), "foo.h1");
+        assertEquals(456, foo.get("h2"), "foo.h2");
+
+        assertEquals("TheBody", myBean.body, "body");
+    }
+
+    @Override
+    protected Registry createRegistry() throws Exception {
+        Registry answer = super.createRegistry();
+        answer.bind("myBean", myBean);
+        return answer;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("direct:in").to("bean:myBean?method=myMethod");
+            }
+        };
+    }
+
+    public static class MyBean {
+        public Map<String, Object> variables;
+        public Object body;
+
+        @Override
+        public String toString() {
+            return "MyBean[foo: " + variables + " body: " + body + "]";
+        }
+
+        public void myMethod(@Variables Map<String, Object> variables, Object 
body) {
+            this.variables = variables;
+            this.body = body;
+            LOG.info("myMethod() method called on {}", this);
+        }
+
+        public void anotherMethod(@Variables Map<String, Object> variables, 
Object body) {
+            fail("Should not have called this method!");
+        }
+    }
+}
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/processor/MethodFilterVariableTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/processor/MethodFilterVariableTest.java
new file mode 100644
index 00000000000..15a510d7c3a
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/processor/MethodFilterVariableTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.processor;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.ExchangePattern;
+import org.apache.camel.Variable;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.Registry;
+import org.junit.jupiter.api.Test;
+
+public class MethodFilterVariableTest extends ContextTestSupport {
+    @Test
+    public void testSendMatchingMessage() throws Exception {
+        String body = "<person name='James' city='London'/>";
+        getMockEndpoint("mock:result").expectedBodiesReceived(body);
+
+        template.sendBodyAndHeader("direct:start", ExchangePattern.InOut, 
body, "city", "London");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testSendNotMatchingMessage() throws Exception {
+        String body = "<person name='Hiram' city='Tampa'/>";
+        getMockEndpoint("mock:result").expectedMessageCount(0);
+
+        template.sendBodyAndHeader("direct:start", ExchangePattern.InOut, 
body, "city", "Tampa");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                // START SNIPPET: example
+                from("direct:start")
+                        .setVariable("location", header("city"))
+                        .filter().method("myBean", 
"matches").to("mock:result");
+                // END SNIPPET: example
+            }
+        };
+    }
+
+    @Override
+    protected Registry createRegistry() throws Exception {
+        Registry answer = super.createRegistry();
+        answer.bind("myBean", new MyBean());
+        return answer;
+    }
+
+    // START SNIPPET: filter
+    public static class MyBean {
+        public boolean matches(@Variable("location") String location) {
+            return "London".equals(location);
+        }
+    }
+    // END SNIPPET: filter
+}
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 820c42dfbce..2b00ee42e3b 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
@@ -178,6 +178,102 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an expression for the variable with the given name
+     *
+     * @param  variableName the name of the variable the expression will return
+     * @return              an expression object which will return the 
variable value
+     */
+    public static Expression variableExpression(final String variableName) {
+        return variableExpression(simpleExpression(variableName));
+    }
+
+    /**
+     * Returns an expression for the variable with the given name
+     *
+     * @param  variableName the name of the variable the expression will return
+     * @return              an expression object which will return the 
variable value
+     */
+    public static Expression variableExpression(final Expression variableName) 
{
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                String name = variableName.evaluate(exchange, String.class);
+                return exchange.getVariable(name);
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                variableName.init(context);
+            }
+
+            @Override
+            public String toString() {
+                return "variable(" + variableName + ")";
+            }
+        };
+    }
+
+    /**
+     * Returns an expression for the variable with the given name converted to 
the given type
+     *
+     * @param  variableName the name of the variable the expression will return
+     * @param  type         the type to convert to
+     * @return              an expression object which will return the 
variable value
+     */
+    public static <T> Expression variableExpression(final String variableName, 
final Class<T> type) {
+        return variableExpression(simpleExpression(variableName), 
constantExpression(type.getName()));
+    }
+
+    /**
+     * Returns an expression for the variable with the given name converted to 
the given type
+     *
+     * @param  variableName the name of the variable the expression will return
+     * @param  typeName     the type to convert to as a FQN class name
+     * @return              an expression object which will return the header 
value
+     */
+    public static Expression varibleExpression(final String variableName, 
final String typeName) {
+        return variableExpression(simpleExpression(variableName), 
simpleExpression(typeName));
+    }
+
+    /**
+     * Returns an expression for the variable with the given name converted to 
the given type
+     *
+     * @param  variableName the name of the variable the expression will return
+     * @param  typeName     the type to convert to as a FQN class name
+     * @return              an expression object which will return the header 
value
+     */
+    public static Expression variableExpression(final Expression variableName, 
final Expression typeName) {
+        return new ExpressionAdapter() {
+            private ClassResolver classResolver;
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                Class<?> type;
+                try {
+                    String text = typeName.evaluate(exchange, String.class);
+                    type = classResolver.resolveMandatoryClass(text);
+                } catch (ClassNotFoundException e) {
+                    throw 
CamelExecutionException.wrapCamelExecutionException(exchange, e);
+                }
+                String text = variableName.evaluate(exchange, String.class);
+                return exchange.getVariable(text, type);
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                variableName.init(context);
+                typeName.init(context);
+                classResolver = context.getClassResolver();
+            }
+
+            @Override
+            public String toString() {
+                return "variableAs(" + variableName + ", " + typeName + ")";
+            }
+        };
+    }
+
     /**
      * Returns an expression for the inbound message headers
      *
@@ -197,6 +293,25 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an expression for variables
+     *
+     * @return an expression object which will return the variables
+     */
+    public static Expression variablesExpression() {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                return exchange.getVariables();
+            }
+
+            @Override
+            public String toString() {
+                return "variables";
+            }
+        };
+    }
+
     /**
      * Returns an expression for the exchange pattern
      *
diff --git 
a/docs/user-manual/modules/ROOT/pages/parameter-binding-annotations.adoc 
b/docs/user-manual/modules/ROOT/pages/parameter-binding-annotations.adoc
index 03861ef0ad2..6afb0d5a88c 100644
--- a/docs/user-manual/modules/ROOT/pages/parameter-binding-annotations.adoc
+++ b/docs/user-manual/modules/ROOT/pages/parameter-binding-annotations.adoc
@@ -9,11 +9,18 @@ The bean parameter binding annotations from Camel are as 
follows:
 |To bind to an inbound message body | 
 
 |`org.apache.camel.Header`
-|To bind to an message header |String name of the header
+|To bind to a message header |String name of the header
 
 |`org.apache.camel.Headers`
 |To bind to the Map of the message headers |
 
+|`org.apache.camel.Variable`
+|To bind to a named variable |String name of the
+variable
+
+|`org.apache.camel.Variables`
+|To bind to the variables map |
+
 |`org.apache.camel.ExchangeProperty`
 |To bind to a named property on the exchange |String name of the
 property

Reply via email to