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

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

commit 8cb9fd6242f825b616f8b59966661f8de01ea604
Author: Claus Ibsen <claus.ib...@gmail.com>
AuthorDate: Tue Feb 4 15:51:23 2025 +0100

    CAMEL-21703: Add external functions to simple language such as 
camel-attachments
---
 components/camel-attachments/pom.xml               |  11 +
 .../simple-function-factory/camel-attachments      |   2 +
 .../src/main/docs/attachments.adoc                 |   2 +-
 .../attachment/AttachmentExpressionBuilder.java    | 225 +++++++++++++++
 .../camel/attachment/CSimpleAttachmentHelper.java  |  95 +++++++
 .../camel/attachment/CamelFileDataSource.java      |   7 +
 .../attachment/NoSuchAttachmentException.java      |  62 +++++
 .../camel/attachment/SimpleAttachmentFunction.java | 301 +++++++++++++++++++++
 components/camel-attachments/src/test/data/123.txt |   1 +
 .../camel/attachment/SimpleAttachmentTest.java     | 137 ++++++++++
 components/camel-csimple-joor/pom.xml              |   5 +
 .../language/csimple/joor/JoorCSimpleCompiler.java |  30 +-
 .../camel-csimple-joor/src/test/data/123.txt       |   1 +
 .../camel-csimple-joor/src/test/data/message1.xml  |  24 ++
 .../camel-csimple-joor/src/test/data/message2.xml  |  24 ++
 .../csimple/joor/CSimpleAttachmentsTest.java       | 107 ++++++++
 .../org/apache/camel/spi/BeanProxyFactory.java     |   2 +-
 .../camel/spi/SimpleLanguageFunctionFactory.java   |  55 ++++
 .../modules/languages/pages/simple-language.adoc   |  25 ++
 .../language/csimple/CSimpleCodeGenerator.java     |  19 +-
 .../language/csimple/CSimpleExpressionParser.java  |   7 +-
 .../camel/language/csimple/CSimpleHelper.java      |   4 +
 .../language/csimple/CSimplePredicateParser.java   |   7 +-
 .../language/simple/SimpleExpressionBuilder.java   |   8 +-
 .../language/simple/SimpleExpressionParser.java    |   2 +-
 .../language/simple/SimplePredicateParser.java     |   2 +-
 .../camel/language/simple/ast/BaseSimpleNode.java  |   6 +-
 .../language/simple/ast/BinaryExpression.java      |  10 +-
 .../language/simple/ast/BooleanExpression.java     |   2 +-
 .../camel/language/simple/ast/CompositeNodes.java  |   6 +-
 .../camel/language/simple/ast/DoubleQuoteEnd.java  |   2 +-
 .../language/simple/ast/DoubleQuoteStart.java      |   4 +-
 .../language/simple/ast/LiteralExpression.java     |   2 +-
 .../language/simple/ast/LogicalExpression.java     |  10 +-
 .../camel/language/simple/ast/NullExpression.java  |   2 +-
 .../language/simple/ast/NumericExpression.java     |   2 +-
 .../language/simple/ast/SimpleFunctionEnd.java     |   2 +-
 .../simple/ast/SimpleFunctionExpression.java       |  65 ++++-
 .../language/simple/ast/SimpleFunctionStart.java   |  18 +-
 .../camel/language/simple/ast/SimpleNode.java      |   3 +-
 .../camel/language/simple/ast/SingleQuoteEnd.java  |   2 +-
 .../language/simple/ast/SingleQuoteStart.java      |   4 +-
 .../camel/language/simple/ast/UnaryExpression.java |   8 +-
 .../apache/camel/language/simple/SimpleTest.java   |   2 +
 .../camel/support/builder/ExpressionBuilder.java   |  34 +++
 45 files changed, 1290 insertions(+), 59 deletions(-)

diff --git a/components/camel-attachments/pom.xml 
b/components/camel-attachments/pom.xml
index f6849893c7d..d9e98326aaa 100644
--- a/components/camel-attachments/pom.xml
+++ b/components/camel-attachments/pom.xml
@@ -44,6 +44,11 @@
             <artifactId>camel-support</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-core-languages</artifactId>
+            <version>${project.version}</version>
+        </dependency>
 
         <!-- attachments api -->
         <dependency>
@@ -58,6 +63,12 @@
             <artifactId>camel-test-junit5</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-bean</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
 
     </dependencies>
 </project>
diff --git 
a/components/camel-attachments/src/generated/resources/META-INF/services/org/apache/camel/simple-function-factory/camel-attachments
 
b/components/camel-attachments/src/generated/resources/META-INF/services/org/apache/camel/simple-function-factory/camel-attachments
new file mode 100644
index 00000000000..7e9b7cb00c9
--- /dev/null
+++ 
b/components/camel-attachments/src/generated/resources/META-INF/services/org/apache/camel/simple-function-factory/camel-attachments
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.attachment.SimpleAttachmentFunction
diff --git a/components/camel-attachments/src/main/docs/attachments.adoc 
b/components/camel-attachments/src/main/docs/attachments.adoc
index 1c552cf1124..5cd8c5cf159 100644
--- a/components/camel-attachments/src/main/docs/attachments.adoc
+++ b/components/camel-attachments/src/main/docs/attachments.adoc
@@ -27,5 +27,5 @@ And if you want to add an attachment, to a Camel `Message` 
you can do as shown:
 [source,java]
 ----
 AttachmentMessage attMsg = exchange.getIn(AttachmentMessage.class);
-attMsg.addAttachment("message1.xml", new DataHandler(new FileDataSource(new 
File("myMessage1.xml"))));
+attMsg.addAttachment("message1.xml", new DataHandler(new 
CamelFileDataSource(new File("myMessage1.xml"))));
 ----
diff --git 
a/components/camel-attachments/src/main/java/org/apache/camel/attachment/AttachmentExpressionBuilder.java
 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/AttachmentExpressionBuilder.java
new file mode 100644
index 00000000000..d86f06e6664
--- /dev/null
+++ 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/AttachmentExpressionBuilder.java
@@ -0,0 +1,225 @@
+/*
+ * 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.attachment;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.Exchange;
+import org.apache.camel.Expression;
+import org.apache.camel.NoTypeConversionAvailableException;
+import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.language.simple.SimpleExpressionBuilder;
+import org.apache.camel.support.ExpressionAdapter;
+import org.apache.camel.support.ObjectHelper;
+
+import static 
org.apache.camel.support.builder.ExpressionBuilder.simpleExpression;
+
+public class AttachmentExpressionBuilder {
+
+    public static Expression attachments() {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                if (exchange.getMessage() instanceof AttachmentMessage am) {
+                    return am.getAttachments();
+                }
+                return null;
+            }
+        };
+    }
+
+    public static Expression attachmentsSize() {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                if (exchange.getMessage() instanceof AttachmentMessage am) {
+                    return am.getAttachments().size();
+                }
+                return 0;
+            }
+        };
+    }
+
+    public static Expression attachmentContent(final String key, final String 
type) {
+        return new ExpressionAdapter() {
+            private Class<?> clazz;
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                Object answer = null;
+                if (exchange.getMessage() instanceof AttachmentMessage am) {
+                    var dh = am.getAttachment(key);
+                    try {
+                        answer = dh.getContent();
+                    } catch (Exception e) {
+                        throw new RuntimeException(e);
+                    }
+                    if (answer != null && clazz != null) {
+                        try {
+                            answer = 
exchange.getContext().getTypeConverter().mandatoryConvertTo(clazz, answer);
+                        } catch (NoTypeConversionAvailableException e) {
+                            throw 
CamelExecutionException.wrapCamelExecutionException(exchange, e);
+                        }
+                    }
+                }
+                return answer;
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                if (type != null) {
+                    try {
+                        clazz = 
context.getClassResolver().resolveMandatoryClass(type);
+                    } catch (ClassNotFoundException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
+    }
+
+    public static Expression attachmentContentHeader(final String key, final 
String name, final String type) {
+        return new ExpressionAdapter() {
+            private Class<?> clazz;
+
+            @Override
+            public Object evaluate(Exchange exchange) {
+                Object answer = null;
+                if (exchange.getMessage() instanceof AttachmentMessage am) {
+                    var ao = am.getAttachmentObject(key);
+                    if (ao != null) {
+                        answer = ao.getHeader(name);
+                        if (answer != null && clazz != null) {
+                            try {
+                                answer = 
exchange.getContext().getTypeConverter().mandatoryConvertTo(clazz, answer);
+                            } catch (NoTypeConversionAvailableException e) {
+                                throw 
CamelExecutionException.wrapCamelExecutionException(exchange, e);
+                            }
+                        }
+                    }
+                }
+                return answer;
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                if (type != null) {
+                    try {
+                        clazz = 
context.getClassResolver().resolveMandatoryClass(type);
+                    } catch (ClassNotFoundException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
+    }
+
+    public static Expression attachmentContentType(final String key) {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                if (exchange.getMessage() instanceof AttachmentMessage am) {
+                    var dh = am.getAttachment(key);
+                    if (dh != null) {
+                        return dh.getContentType();
+                    }
+                }
+                return null;
+            }
+        };
+    }
+
+    /**
+     * Returns an expression for the attachment value of exchange with the 
given name
+     *
+     * @param  attachmentName the name of the attachment the expression will 
return
+     * @return                an expression object which will return the 
property value
+     */
+    public static Expression attachmentExpression(final String attachmentName) 
{
+        return attachmentExpression(simpleExpression(attachmentName), false);
+    }
+
+    /**
+     * Returns an expression for the attachment value of exchange with the 
given name
+     *
+     * @param  attachmentName the name of the attachment the expression will 
return
+     * @param  mandatory      whether the attachment is mandatory and if not 
present an exception is thrown
+     * @return                an expression object which will return the 
attachment value
+     */
+    public static Expression attachmentExpression(final Expression 
attachmentName, final boolean mandatory) {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                if (exchange.getMessage() instanceof AttachmentMessage am) {
+                    String key = attachmentName.evaluate(exchange, 
String.class);
+                    Object answer = am.getAttachment(key);
+                    if (mandatory && answer == null) {
+                        throw 
RuntimeCamelException.wrapRuntimeCamelException(new 
NoSuchAttachmentException(exchange, key));
+                    }
+                    return answer;
+                }
+                return null;
+            }
+
+            @Override
+            public void init(CamelContext context) {
+                super.init(context);
+                attachmentName.init(context);
+            }
+
+            @Override
+            public String toString() {
+                return "attachment(" + attachmentName + ")";
+            }
+        };
+    }
+
+    /**
+     * Returns an expression for the attachment value of exchange with the 
given name invoking methods defined in a
+     * simple OGNL notation
+     *
+     * @param ognl methods to invoke on the attachment in a simple OGNL syntax
+     */
+    public static Expression attachmentOgnlExpression(final String ognl) {
+        return new SimpleExpressionBuilder.KeyedOgnlExpressionAdapter(
+                ognl, "attachmentOgnl(" + ognl + ")",
+                (exchange, exp) -> {
+                    if (exchange.getMessage() instanceof AttachmentMessage am) 
{
+                        String text = exp.evaluate(exchange, String.class);
+                        var dh = am.getAttachment(text);
+                        if (dh == null && ObjectHelper.isNumber(text)) {
+                            try {
+                                // fallback to lookup by numeric index
+                                int idx = Integer.parseInt(text);
+                                if (idx < am.getAttachments().size()) {
+                                    var it = 
am.getAttachments().values().iterator();
+                                    for (int i = 0; i < idx; i++) {
+                                        it.next();
+                                    }
+                                    dh = it.next();
+                                }
+                            } catch (NumberFormatException e) {
+                                // ignore
+                            }
+                        }
+                        return dh;
+                    }
+                    return null;
+                });
+    }
+
+}
diff --git 
a/components/camel-attachments/src/main/java/org/apache/camel/attachment/CSimpleAttachmentHelper.java
 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CSimpleAttachmentHelper.java
new file mode 100644
index 00000000000..1252657eadd
--- /dev/null
+++ 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CSimpleAttachmentHelper.java
@@ -0,0 +1,95 @@
+/*
+ * 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.attachment;
+
+import java.util.Map;
+
+import jakarta.activation.DataHandler;
+
+import org.apache.camel.Exchange;
+
+public class CSimpleAttachmentHelper {
+
+    public static Map<String, DataHandler> attachments(Exchange exchange) {
+        if (exchange.getMessage() instanceof AttachmentMessage am) {
+            return am.getAttachments();
+        }
+        return null;
+    }
+
+    public static int attachmentsSize(Exchange exchange) {
+        if (exchange.getMessage() instanceof AttachmentMessage am) {
+            return am.getAttachments().size();
+        }
+        return 0;
+    }
+
+    public static Object attachmentContent(Exchange exchange, String key) 
throws Exception {
+        if (exchange.getMessage() instanceof AttachmentMessage am) {
+            var dh = am.getAttachments().get(key);
+            if (dh != null) {
+                return dh.getContent();
+            }
+        }
+        return null;
+    }
+
+    public static Object attachmentContentAsText(Exchange exchange, String 
key) throws Exception {
+        Object data = attachmentContent(exchange, key);
+        if (data != null) {
+            return 
exchange.getContext().getTypeConverter().convertTo(String.class, exchange, 
data);
+        }
+        return null;
+    }
+
+    public static <T> T attachmentContentAs(Exchange exchange, String key, 
Class<T> type) throws Exception {
+        Object data = attachmentContent(exchange, key);
+        if (data != null) {
+            return exchange.getContext().getTypeConverter().convertTo(type, 
exchange, data);
+        }
+        return null;
+    }
+
+    public static String attachmentContentType(Exchange exchange, String key) {
+        if (exchange.getMessage() instanceof AttachmentMessage am) {
+            var dh = am.getAttachments().get(key);
+            if (dh != null) {
+                return dh.getContentType();
+            }
+        }
+        return null;
+    }
+
+    public static String attachmentHeader(Exchange exchange, String key, 
String name) {
+        if (exchange.getMessage() instanceof AttachmentMessage am) {
+            var ao = am.getAttachmentObjects().get(key);
+            if (ao != null) {
+                return ao.getHeader(name);
+            }
+        }
+        return null;
+    }
+
+    public static <T> T attachmentHeaderAs(Exchange exchange, String key, 
String name, Class<T> type) {
+        String data = attachmentHeader(exchange, key, name);
+        if (data != null) {
+            return exchange.getContext().getTypeConverter().convertTo(type, 
exchange, data);
+        }
+        return null;
+    }
+
+}
diff --git 
a/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java
 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java
index 1f26d9d6db7..3c2f1676f9c 100644
--- 
a/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java
+++ 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java
@@ -21,10 +21,17 @@ import java.io.File;
 import jakarta.activation.FileDataSource;
 import jakarta.activation.FileTypeMap;
 
+/**
+ * A {@link FileDataSource} that uses the file name/extension to determine the 
content-type.
+ */
 public class CamelFileDataSource extends FileDataSource {
     private final String fileName;
     private FileTypeMap typeMap;
 
+    public CamelFileDataSource(File file) {
+        this(file, file.getName());
+    }
+
     public CamelFileDataSource(File file, String fileName) {
         super(file);
         this.fileName = fileName;
diff --git 
a/components/camel-attachments/src/main/java/org/apache/camel/attachment/NoSuchAttachmentException.java
 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/NoSuchAttachmentException.java
new file mode 100644
index 00000000000..d1452e0b962
--- /dev/null
+++ 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/NoSuchAttachmentException.java
@@ -0,0 +1,62 @@
+/*
+ * 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.attachment;
+
+import org.apache.camel.CamelExchangeException;
+import org.apache.camel.Exchange;
+
+/**
+ * An exception caused when a mandatory attachment is not available on a 
message {@link Exchange}
+ *
+ * @see org.apache.camel.support.ExchangeHelper#getMandatoryProperty(Exchange, 
String, Class)
+ */
+public class NoSuchAttachmentException extends CamelExchangeException {
+
+    private final String attachmentName;
+    private final transient Class<?> type;
+
+    public NoSuchAttachmentException(Exchange exchange, String attachmentName) 
{
+        this(exchange, attachmentName, null);
+    }
+
+    public NoSuchAttachmentException(Exchange exchange, String attachmentName, 
Class<?> type) {
+        super("No '" + attachmentName + "' attachment available" + (type != 
null ? " of type: " + type.getName() : "")
+              + reason(exchange, attachmentName), exchange);
+        this.attachmentName = attachmentName;
+        this.type = type;
+    }
+
+    public String getAttachmentName() {
+        return attachmentName;
+    }
+
+    public Class<?> getType() {
+        return type;
+    }
+
+    protected static String reason(Exchange exchange, String propertyName) {
+        Object value = exchange.getProperty(propertyName);
+        return valueDescription(value);
+    }
+
+    static String valueDescription(Object value) {
+        if (value == null) {
+            return "";
+        }
+        return " but has type: " + value.getClass().getCanonicalName();
+    }
+}
diff --git 
a/components/camel-attachments/src/main/java/org/apache/camel/attachment/SimpleAttachmentFunction.java
 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/SimpleAttachmentFunction.java
new file mode 100644
index 00000000000..c0accf73c8a
--- /dev/null
+++ 
b/components/camel-attachments/src/main/java/org/apache/camel/attachment/SimpleAttachmentFunction.java
@@ -0,0 +1,301 @@
+/*
+ * 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.attachment;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+import org.apache.camel.language.simple.types.SimpleParserException;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.OgnlHelper;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.util.StringQuoteHelper;
+
+@JdkService(SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments")
+public class SimpleAttachmentFunction implements SimpleLanguageFunctionFactory 
{
+
+    @Override
+    public Expression createFunction(CamelContext camelContext, String 
function, int index) {
+        if ("attachments".equals(function)) {
+            return AttachmentExpressionBuilder.attachments();
+        } else if ("attachments.size".equals(function) || 
"attachments.size()".equals(function)
+                || "attachments.length".equals(function) || 
"attachments.length()".equals(function)) {
+            return AttachmentExpressionBuilder.attachmentsSize();
+        }
+
+        String remainder = ifStartsWithReturnRemainder("attachmentContent(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContent(key)} was: "
+                                                + function,
+                        index);
+            }
+            return AttachmentExpressionBuilder.attachmentContent(values, null);
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentContentAs(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentAs(key,type)} was: "
+                                                + function,
+                        index);
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentAs(key,type)} was: " 
+ function, index);
+            }
+            String key = tokens[0];
+            String type = tokens[1];
+            return AttachmentExpressionBuilder.attachmentContent(key, type);
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentContentAsText(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentAsText(key)}} was: "
+                                                + function,
+                        index);
+            }
+            return AttachmentExpressionBuilder.attachmentContent(values, 
"String");
+        }
+
+        remainder = ifStartsWithReturnRemainder("attachmentHeaderAs(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeaderAs(key,name,type)} 
was: " + function, index);
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length != 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeaderAs(key,name,type)} 
was: " + function,
+                        index);
+            }
+            String key = tokens[0];
+            String name = tokens[1];
+            String type = tokens[2];
+            return AttachmentExpressionBuilder.attachmentContentHeader(key, 
name, type);
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentHeader(", function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeader(key,name)} was: "
+                                                + function,
+                        index);
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length != 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeader(key,name)} was: " + 
function,
+                        index);
+            }
+            String key = tokens[0];
+            String name = tokens[1];
+            return AttachmentExpressionBuilder.attachmentContentHeader(key, 
name, null);
+        }
+
+        remainder = ifStartsWithReturnRemainder("attachmentContentType(", 
function);
+        if (remainder != null) {
+            String key = StringHelper.before(remainder, ")");
+            if (key == null || ObjectHelper.isEmpty(key)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentType(key)} was: "
+                                                + function,
+                        index);
+            }
+            return AttachmentExpressionBuilder.attachmentContentType(key);
+        }
+
+        remainder = ifStartsWithReturnRemainder("attachment", function);
+        if (remainder != null) {
+            // remove leading character (dot, colon or ?)
+            if (remainder.startsWith(".") || remainder.startsWith(":") || 
remainder.startsWith("?")) {
+                remainder = remainder.substring(1);
+            }
+            // remove starting and ending brackets
+            if (remainder.startsWith("[") && remainder.endsWith("]")) {
+                remainder = remainder.substring(1, remainder.length() - 1);
+            }
+
+            // validate syntax
+            boolean invalid = 
OgnlHelper.isInvalidValidOgnlExpression(remainder);
+            if (invalid) {
+                throw new SimpleParserException("Valid syntax: 
${attachment.OGNL} was: " + function, index);
+            }
+
+            if (OgnlHelper.isValidOgnlExpression(remainder)) {
+                // ognl based attachment
+                return 
AttachmentExpressionBuilder.attachmentOgnlExpression(remainder);
+            } else {
+                // regular attachment
+                return 
AttachmentExpressionBuilder.attachmentExpression(remainder);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String createCode(CamelContext camelContext, String function, int 
index) {
+        if ("attachments".equals(function)) {
+            return "attachments(exchange)";
+        } else if ("attachments.size".equals(function) || 
"attachments.size()".equals(function)
+                || "attachments.length".equals(function) || 
"attachments.length()".equals(function)) {
+            return "attachmentsSize(exchange)";
+        }
+
+        String remainder = ifStartsWithReturnRemainder("attachmentContent(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContent(key)} was: "
+                                                + function,
+                        index);
+            }
+            String key = StringHelper.removeQuotes(values);
+            key = key.trim();
+            return "attachmentContent(exchange, \"" + key + "\")";
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentContentAs(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentAs(key,type)} was: "
+                                                + function,
+                        index);
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length > 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentAs(key,type)} was: " 
+ function, index);
+            }
+            String key = tokens[0];
+            String type = tokens[1];
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            return "attachmentContentAs(exchange, \"" + key + "\", " + type + 
")";
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentContentAsText(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentAsText(key)}} was: "
+                                                + function,
+                        index);
+            }
+            String key = StringHelper.removeQuotes(values);
+            key = key.trim();
+            return "attachmentContentAsText(exchange, \"" + key + "\")";
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentContentType(", 
function);
+        if (remainder != null) {
+            String key = StringHelper.before(remainder, ")");
+            if (key == null || ObjectHelper.isEmpty(key)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentContentType(key)} was: "
+                                                + function,
+                        index);
+            }
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            return "attachmentContentType(exchange, \"" + key + "\")";
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentHeader(", function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeader(key,name)} was: "
+                                                + function,
+                        index);
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length != 2) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeader(key,name)} was: " + 
function, index);
+            }
+            String key = tokens[0];
+            String name = tokens[1];
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            name = StringHelper.removeQuotes(name);
+            name = name.trim();
+            return "attachmentHeader(exchange, \"" + key + "\", \"" + name + 
"\")";
+        }
+        remainder = ifStartsWithReturnRemainder("attachmentHeaderAs(", 
function);
+        if (remainder != null) {
+            String values = StringHelper.before(remainder, ")");
+            if (values == null || ObjectHelper.isEmpty(values)) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeaderAs(key,name,type)} 
was: "
+                                                + function,
+                        index);
+            }
+            String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', 
false);
+            if (tokens.length != 3) {
+                throw new SimpleParserException(
+                        "Valid syntax: ${attachmentHeaderAs(key,name,type)} 
was: " + function, index);
+            }
+            String key = tokens[0];
+            String name = tokens[1];
+            String type = tokens[2];
+            key = StringHelper.removeQuotes(key);
+            key = key.trim();
+            name = StringHelper.removeQuotes(name);
+            name = name.trim();
+            type = appendClass(type);
+            type = type.replace('$', '.');
+            type = type.trim();
+            return "attachmentHeaderAs(exchange, \"" + key + "\", \"" + name + 
"\", " + type + ")";
+        }
+
+        return null;
+    }
+
+    private String ifStartsWithReturnRemainder(String prefix, String text) {
+        if (text.startsWith(prefix)) {
+            String remainder = text.substring(prefix.length());
+            if (!remainder.isEmpty()) {
+                return remainder;
+            }
+        }
+        return null;
+    }
+
+    private static String appendClass(String type) {
+        type = StringHelper.removeQuotes(type);
+        if (!type.endsWith(".class")) {
+            type = type + ".class";
+        }
+        return type;
+    }
+
+}
diff --git a/components/camel-attachments/src/test/data/123.txt 
b/components/camel-attachments/src/test/data/123.txt
new file mode 100644
index 00000000000..ee2b8364542
--- /dev/null
+++ b/components/camel-attachments/src/test/data/123.txt
@@ -0,0 +1 @@
+456
\ No newline at end of file
diff --git 
a/components/camel-attachments/src/test/java/org/apache/camel/attachment/SimpleAttachmentTest.java
 
b/components/camel-attachments/src/test/java/org/apache/camel/attachment/SimpleAttachmentTest.java
new file mode 100644
index 00000000000..97221b87bd0
--- /dev/null
+++ 
b/components/camel-attachments/src/test/java/org/apache/camel/attachment/SimpleAttachmentTest.java
@@ -0,0 +1,137 @@
+/*
+ * 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.attachment;
+
+import java.io.File;
+
+import jakarta.activation.DataHandler;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.test.junit5.LanguageTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class SimpleAttachmentTest extends LanguageTestSupport {
+
+    @Override
+    protected String getLanguageName() {
+        return "simple";
+    }
+
+    @Override
+    protected void populateExchange(Exchange exchange) {
+        super.populateExchange(exchange);
+
+        DefaultAttachment da
+                = new DefaultAttachment(new DataHandler(new 
CamelFileDataSource(new File("src/test/data/message1.xml"))));
+        da.addHeader("orderId", "123");
+        
exchange.getIn(AttachmentMessage.class).addAttachmentObject("message1.xml", da);
+
+        da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new 
File("src/test/data/message2.xml"))));
+        da.addHeader("orderId", "456");
+        
exchange.getIn(AttachmentMessage.class).addAttachmentObject("message2.xml", da);
+
+        da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new 
File("src/test/data/123.txt"))));
+        da.addHeader("orderId", "0");
+        exchange.getIn(AttachmentMessage.class).addAttachmentObject("123.txt", 
da);
+    }
+
+    @Test
+    public void testAttachments() throws Exception {
+        var map = 
exchange.getMessage(AttachmentMessage.class).getAttachments();
+        assertExpression("${attachments}", map);
+        assertExpression("${attachments.size}", 3);
+        assertExpression("${attachments.length}", 3);
+
+        
exchange.getMessage(AttachmentMessage.class).removeAttachment("message1.xml");
+        assertExpression("${attachments.size}", 2);
+        assertExpression("${attachments.length}", 2);
+
+        
exchange.getMessage(AttachmentMessage.class).removeAttachment("message2.xml");
+        assertExpression("${attachments.size}", 1);
+        assertExpression("${attachments.length}", 1);
+
+        
exchange.getMessage(AttachmentMessage.class).removeAttachment("123.txt");
+        assertExpression("${attachments.size}", 0);
+        assertExpression("${attachments.length}", 0);
+    }
+
+    @Test
+    public void testAttachmentContent() throws Exception {
+        var map = 
exchange.getMessage(AttachmentMessage.class).getAttachments();
+        Object is1 = map.get("message1.xml").getContent();
+        String xml1 = context.getTypeConverter().convertTo(String.class, is1);
+        Object is2 = map.get("message2.xml").getContent();
+        String xml2 = context.getTypeConverter().convertTo(String.class, is2);
+
+        assertExpression("${attachmentContent(message1.xml)}", xml1);
+        assertExpression("${attachmentContentAs(message2.xml,String)}", xml2);
+        assertExpression("${attachmentContentAsText(message2.xml)}", xml2);
+        assertExpression("${attachmentContent(123.txt)}", "456");
+        assertExpression("${attachmentContentAs(123.txt,String)}", "456");
+        assertExpression("${attachmentContentAsText(123.txt)}", "456");
+    }
+
+    @Test
+    public void testAttachmentContentHeader() throws Exception {
+        assertExpression("${attachmentHeader(message1.xml,orderId)}", "123");
+        assertExpression("${attachmentHeader(message1.xml,foo)}", null);
+        assertExpression("${attachmentHeaderAs(message2.xml,orderId,int)}", 
456);
+        assertExpression("${attachmentHeader(123.txt,orderId)}", 0);
+        assertExpression("${attachmentHeader(123.txt,unknown)}", null);
+    }
+
+    @Test
+    public void testAttachmentContentType() throws Exception {
+        assertExpression("${attachmentContentType(message1.xml)}", 
"application/octet-stream");
+        assertExpression("${attachmentContentType(message2.xml)}", 
"application/octet-stream");
+        assertExpression("${attachmentContentType(123.txt)}", 
"application/octet-stream");
+    }
+
+    @Test
+    public void testAttachmentOgnlByName() throws Exception {
+        var map = 
exchange.getMessage(AttachmentMessage.class).getAttachments();
+        Object is1 = map.get("message1.xml").getContent();
+        String xml1 = context.getTypeConverter().convertTo(String.class, is1);
+        Object is2 = map.get("message2.xml").getContent();
+        String xml2 = context.getTypeConverter().convertTo(String.class, is2);
+
+        assertExpression("${attachment[message1.xml].name}", "message1.xml");
+        assertExpression("${attachment[message1.xml].contentType}", 
"application/octet-stream");
+        assertExpression("${attachment[message1.xml].content}", xml1);
+
+        assertExpression("${attachment[message2.xml].name}", "message2.xml");
+        assertExpression("${attachment[message2.xml].contentType}", 
"application/octet-stream");
+        assertExpression("${attachment[message2.xml].content}", xml2);
+
+        assertExpression("${attachment[123.txt].name}", "123.txt");
+        assertExpression("${attachment[123.txt].contentType}", 
"application/octet-stream");
+        assertExpression("${attachment[123.txt].content}", "456");
+    }
+
+    @Test
+    public void testAttachmentOgnlByIndex() throws Exception {
+        assertExpression("${attachment[0].name}", "message1.xml");
+        assertExpression("${attachment[0].contentType}", 
"application/octet-stream");
+
+        assertExpression("${attachment[1].name}", "message2.xml");
+        assertExpression("${attachment[1].contentType}", 
"application/octet-stream");
+
+        assertExpression("${attachment[2].name}", "123.txt");
+        assertExpression("${attachment[2].contentType}", 
"application/octet-stream");
+        assertExpression("${attachment[2].content}", "456");
+    }
+}
diff --git a/components/camel-csimple-joor/pom.xml 
b/components/camel-csimple-joor/pom.xml
index 5ee38dd7216..79a9946b6f2 100644
--- a/components/camel-csimple-joor/pom.xml
+++ b/components/camel-csimple-joor/pom.xml
@@ -62,6 +62,11 @@
             <type>test-jar</type>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-attachments</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.hamcrest</groupId>
             <artifactId>hamcrest</artifactId>
diff --git 
a/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java
 
b/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java
index 4ef12b57527..1ba78af5813 100644
--- 
a/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java
+++ 
b/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java
@@ -17,17 +17,21 @@
 package org.apache.camel.language.csimple.joor;
 
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.StaticService;
 import org.apache.camel.language.csimple.CSimpleCodeGenerator;
 import org.apache.camel.language.csimple.CSimpleCompiler;
 import org.apache.camel.language.csimple.CSimpleExpression;
 import org.apache.camel.language.csimple.CSimpleGeneratedCode;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
 import org.apache.camel.spi.annotations.JdkService;
+import org.apache.camel.support.ResolverHelper;
 import org.apache.camel.support.ScriptHelper;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.StopWatch;
@@ -40,15 +44,26 @@ import org.slf4j.LoggerFactory;
  * jOOR compiler for csimple language.
  */
 @JdkService(CSimpleCompiler.FACTORY)
-public class JoorCSimpleCompiler extends ServiceSupport implements 
CSimpleCompiler, StaticService {
+public class JoorCSimpleCompiler extends ServiceSupport implements 
CSimpleCompiler, CamelContextAware, StaticService {
 
     private static final Logger LOG = 
LoggerFactory.getLogger(JoorCSimpleCompiler.class);
     private static final AtomicInteger UUID = new AtomicInteger();
+    private CamelContext camelContext;
     private Set<String> imports = new TreeSet<>();
     private Map<String, String> aliases;
     private int counter;
     private long taken;
 
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
     public Set<String> getImports() {
         return imports;
     }
@@ -127,6 +142,7 @@ public class JoorCSimpleCompiler extends ServiceSupport 
implements CSimpleCompil
         script = script.trim();
 
         CSimpleCodeGenerator generator = new CSimpleCodeGenerator();
+        generator.setCamelContext(camelContext);
         if (aliases != null && !aliases.isEmpty()) {
             generator.setAliases(aliases);
         }
@@ -150,7 +166,17 @@ public class JoorCSimpleCompiler extends ServiceSupport 
implements CSimpleCompil
 
     @Override
     protected void doStart() throws Exception {
-        // noop
+        // check if camel-attachment is on classpath which then includes 
custom csimple functions
+        if (camelContext != null) {
+            Optional<SimpleLanguageFunctionFactory> factory = 
ResolverHelper.resolveService(
+                    camelContext,
+                    
camelContext.getCamelContextExtension().getBootstrapFactoryFinder(),
+                    SimpleLanguageFunctionFactory.FACTORY + 
"/camel-attachments",
+                    SimpleLanguageFunctionFactory.class);
+            if (factory.isPresent()) {
+                addImport("import static 
org.apache.camel.attachment.CSimpleAttachmentHelper.*");
+            }
+        }
     }
 
     @Override
diff --git a/components/camel-csimple-joor/src/test/data/123.txt 
b/components/camel-csimple-joor/src/test/data/123.txt
new file mode 100644
index 00000000000..ee2b8364542
--- /dev/null
+++ b/components/camel-csimple-joor/src/test/data/123.txt
@@ -0,0 +1 @@
+456
\ No newline at end of file
diff --git a/components/camel-csimple-joor/src/test/data/message1.xml 
b/components/camel-csimple-joor/src/test/data/message1.xml
new file mode 100644
index 00000000000..1a85d067b42
--- /dev/null
+++ b/components/camel-csimple-joor/src/test/data/message1.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<person user="james">
+  <firstName>James</firstName>
+  <lastName>Strachan</lastName>
+  <city>London</city>
+</person>
\ No newline at end of file
diff --git a/components/camel-csimple-joor/src/test/data/message2.xml 
b/components/camel-csimple-joor/src/test/data/message2.xml
new file mode 100644
index 00000000000..73c88bfa2e5
--- /dev/null
+++ b/components/camel-csimple-joor/src/test/data/message2.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+    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.
+
+-->
+<person user="hiram">
+  <firstName>Hiram</firstName>
+  <lastName>Chirino</lastName>
+  <city>Tampa</city>
+</person>
\ No newline at end of file
diff --git 
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/CSimpleAttachmentsTest.java
 
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/CSimpleAttachmentsTest.java
new file mode 100644
index 00000000000..ded77bf8737
--- /dev/null
+++ 
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/CSimpleAttachmentsTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.csimple.joor;
+
+import java.io.File;
+
+import jakarta.activation.DataHandler;
+
+import org.apache.camel.Exchange;
+import org.apache.camel.attachment.AttachmentMessage;
+import org.apache.camel.attachment.CamelFileDataSource;
+import org.apache.camel.attachment.DefaultAttachment;
+import org.apache.camel.test.junit5.LanguageTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class CSimpleAttachmentsTest extends LanguageTestSupport {
+
+    @Override
+    protected String getLanguageName() {
+        return "csimple";
+    }
+
+    @Override
+    protected void populateExchange(Exchange exchange) {
+        super.populateExchange(exchange);
+
+        DefaultAttachment da
+                = new DefaultAttachment(new DataHandler(new 
CamelFileDataSource(new File("src/test/data/message1.xml"))));
+        da.addHeader("orderId", "123");
+        
exchange.getIn(AttachmentMessage.class).addAttachmentObject("message1.xml", da);
+
+        da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new 
File("src/test/data/message2.xml"))));
+        da.addHeader("orderId", "456");
+        
exchange.getIn(AttachmentMessage.class).addAttachmentObject("message2.xml", da);
+
+        da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new 
File("src/test/data/123.txt"))));
+        da.addHeader("orderId", "0");
+        exchange.getIn(AttachmentMessage.class).addAttachmentObject("123.txt", 
da);
+    }
+
+    @Test
+    public void testAttachments() throws Exception {
+        var map = 
exchange.getMessage(AttachmentMessage.class).getAttachments();
+        assertExpression("${attachments}", map);
+        assertExpression("${attachments.size}", 3);
+        assertExpression("${attachments.length}", 3);
+
+        
exchange.getMessage(AttachmentMessage.class).removeAttachment("message1.xml");
+        assertExpression("${attachments.size}", 2);
+        assertExpression("${attachments.length}", 2);
+
+        
exchange.getMessage(AttachmentMessage.class).removeAttachment("message2.xml");
+        assertExpression("${attachments.size}", 1);
+        assertExpression("${attachments.length}", 1);
+
+        
exchange.getMessage(AttachmentMessage.class).removeAttachment("123.txt");
+        assertExpression("${attachments.size}", 0);
+        assertExpression("${attachments.length}", 0);
+    }
+
+    @Test
+    public void testAttachmentContent() throws Exception {
+        var map = 
exchange.getMessage(AttachmentMessage.class).getAttachments();
+        Object is1 = map.get("message1.xml").getContent();
+        String xml1 = context.getTypeConverter().convertTo(String.class, is1);
+        Object is2 = map.get("message2.xml").getContent();
+        String xml2 = context.getTypeConverter().convertTo(String.class, is2);
+
+        assertExpression("${attachmentContent(message1.xml)}", xml1);
+        assertExpression("${attachmentContentAs(message2.xml,String)}", xml2);
+        assertExpression("${attachmentContentAsText(message2.xml)}", xml2);
+        assertExpression("${attachmentContent(123.txt)}", "456");
+        assertExpression("${attachmentContentAs(123.txt,String)}", "456");
+        assertExpression("${attachmentContentAsText(123.txt)}", "456");
+    }
+
+    @Test
+    public void testAttachmentContentHeader() throws Exception {
+        assertExpression("${attachmentHeader(message1.xml,orderId)}", "123");
+        assertExpression("${attachmentHeader(message1.xml,foo)}", null);
+        assertExpression("${attachmentHeaderAs(message2.xml,orderId,int)}", 
456);
+        assertExpression("${attachmentHeader(123.txt,orderId)}", 0);
+        assertExpression("${attachmentHeader(123.txt,unknown)}", null);
+    }
+
+    @Test
+    public void testAttachmentContentType() throws Exception {
+        assertExpression("${attachmentContentType(message1.xml)}", 
"application/octet-stream");
+        assertExpression("${attachmentContentType(message2.xml)}", 
"application/octet-stream");
+        assertExpression("${attachmentContentType(123.txt)}", "text/plain");
+    }
+
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java
index 424ba9c9955..0ac6bd76cec 100644
--- a/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java
@@ -31,7 +31,7 @@ public interface BeanProxyFactory {
     String FACTORY = "bean-proxy-factory";
 
     /**
-     * Creates a proxy bean facaded with the interfaces that when invoked will 
send the data as a message to a Camel
+     * Creates a proxy bean facade with the interfaces that when invoked will 
send the data as a message to a Camel
      * endpoint.
      *
      * @param  endpoint         the endpoint to send to when the proxy is 
invoked
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
new file mode 100644
index 00000000000..3eb0b27c9ce
--- /dev/null
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java
@@ -0,0 +1,55 @@
+/*
+ * 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.spi;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.Expression;
+
+/**
+ * A factory for extending the simple language with functions from external 
components.
+ * <p/>
+ * This requires to have the external component JAR on the classpath.
+ */
+public interface SimpleLanguageFunctionFactory {
+
+    /**
+     * Service factory key.
+     */
+    String FACTORY = "simple-function-factory";
+
+    /**
+     * Creates the {@link Expression} that performs the function.
+     *
+     * @param  camelContext the camel context
+     * @param  function     the function
+     * @param  index        index of the function in the literal input
+     *
+     * @return              the created function as an expression, or 
<tt>null</tt> if not supported by this factory.
+     */
+    Expression createFunction(CamelContext camelContext, String function, int 
index);
+
+    /**
+     * Creates the Java source code that performs the function (for csimple).
+     *
+     * @param  camelContext the camel context
+     * @param  function     the function
+     * @param  index        index of the function in the literal input
+     * @return              the source code or <tt>null</tt> if not supported 
by this factory.
+     */
+    String createCode(CamelContext camelContext, String function, int index);
+
+}
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 e90f73efa7c..3d9afef38d0 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
@@ -128,6 +128,7 @@ Camel OGNL expression.
 classname
 
 |headers |Map |refer to the headers
+|headers.size |int |The number of headers
 
 |variable.foo |Object |refer to the foo variable
 
@@ -140,6 +141,7 @@ value using a Camel OGNL expression.
 classname
 
 |variables |Map |refer to the variables
+|variables.size |int |The number of variables
 
 |exchangeProperty.foo |Object |refer to the foo property on the exchange
 
@@ -321,6 +323,29 @@ true, otherwise the value of `falseExp` is returned. This 
function is similar to
 
 |=======================================================================
 
+== Attachment functions
+
+From *Camel 4.10* onwards then Camel has built-in attachment functions making 
it easy to obtain
+details from attachments stored on the Camel Message such as from HTTP file 
uploads, email with file attachments etc.
+
+This requires having `camel-attachments` JAR on the classpath.
+
+[width="100%",cols="10%,10%,80%",options="header",]
+|=======================================================================
+|Function |Type |Description
+
+|attachments |Map | All the attachments as a `Map<String,DataHandler>`.
+|attachments.size | int | The number of attachments. Is 0 if there are no 
attachments.
+|attachmentContentAsText(key) | String | The content of the attachment as text 
(ie `String`).
+|attachmentContent(key) | Object | The content of the attachment.
+|attachmentContentAs(key,_type_) | Object | The content of the attachment, 
converted to the given type.
+|attachmentHeader(key,name) | Object | The attachment header with the given 
name.
+|attachmentHeaderAs(key,name,_type_) | Object | The attachment header with the 
given name, converted to the given type.
+|attachment[key] | DataHandler | The `DataHandler` for the given attachment.
+|attachment.*OGNL* | Object | refer to the foo attachment on the exchange and 
invoke its value using a Camel OGNL expression.
+|=======================================================================
+
+
 == OGNL expression support
 
 The xref:simple-language.adoc[Simple] and xref:simple-language.adoc[Bean] 
languages support a Camel xref:ognl-language.adoc[OGNL] notation for invoking 
beans in a chain like fashion.
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
index eaf25ee5fb7..f53be1bed93 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java
@@ -22,6 +22,8 @@ import java.util.Set;
 import java.util.TreeSet;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelContextAware;
 import org.apache.camel.util.StringHelper;
 
 /**
@@ -29,13 +31,24 @@ import org.apache.camel.util.StringHelper;
  *
  * @see CSimpleGeneratedCode
  */
-public class CSimpleCodeGenerator {
+public class CSimpleCodeGenerator implements CamelContextAware {
 
     private static final AtomicInteger UUID = new AtomicInteger();
 
+    private CamelContext camelContext;
     private Set<String> imports = new TreeSet<>();
     private Map<String, String> aliases = new HashMap<>();
 
+    @Override
+    public CamelContext getCamelContext() {
+        return camelContext;
+    }
+
+    @Override
+    public void setCamelContext(CamelContext camelContext) {
+        this.camelContext = camelContext;
+    }
+
     public Set<String> getImports() {
         return imports;
     }
@@ -127,14 +140,14 @@ public class CSimpleCodeGenerator {
 
         if (predicate) {
             CSimplePredicateParser parser = new CSimplePredicateParser();
-            script = parser.parsePredicate(script);
+            script = parser.parsePredicate(camelContext, script);
             if (script.isBlank()) {
                 // a predicate that is whitespace is regarded as false
                 script = "false";
             }
         } else {
             CSimpleExpressionParser parser = new CSimpleExpressionParser();
-            script = parser.parseExpression(script);
+            script = parser.parseExpression(camelContext, script);
             if (script.isBlank()) {
                 // an expression can be whitespace, but then we need to wrap 
this in quotes
                 script = "\"" + script + "\"";
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java
index 0922212d514..bc06af83f13 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.language.csimple;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.language.simple.SimpleExpressionParser;
 
 /**
@@ -24,8 +25,12 @@ import 
org.apache.camel.language.simple.SimpleExpressionParser;
 public class CSimpleExpressionParser {
 
     public String parseExpression(String expression) {
+        return parseExpression(null, expression);
+    }
+
+    public String parseExpression(CamelContext camelContext, String 
expression) {
         // reuse simple language parser but output the result as java code
-        SimpleExpressionParser parser = new SimpleExpressionParser(null, 
expression, true, null);
+        SimpleExpressionParser parser = new 
SimpleExpressionParser(camelContext, expression, true, null);
         return parser.parseCode();
     }
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
index e62891dba99..428f79be280 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleHelper.java
@@ -205,6 +205,10 @@ public final class CSimpleHelper {
         return exchange.getVariables();
     }
 
+    public static int variablesSize(Exchange exchange) {
+        return exchange.getVariables().size();
+    }
+
     public static String bodyOneLine(Exchange exchange) {
         String body = exchange.getIn().getBody(String.class);
         if (body == null) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java
index 3ba6f1a5a02..b2ce514f111 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.language.csimple;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.language.simple.SimplePredicateParser;
 
 /**
@@ -24,8 +25,12 @@ import 
org.apache.camel.language.simple.SimplePredicateParser;
 public class CSimplePredicateParser {
 
     public String parsePredicate(String predicate) {
+        return parsePredicate(null, predicate);
+    }
+
+    public String parsePredicate(CamelContext camelContext, String predicate) {
         // reuse simple language parser but output the result as java code
-        SimplePredicateParser parser = new SimplePredicateParser(null, 
predicate, true, null);
+        SimplePredicateParser parser = new SimplePredicateParser(camelContext, 
predicate, true, null);
         return parser.parseCode();
     }
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
index dab6fdb1ec4..e616167f8d1 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionBuilder.java
@@ -1125,9 +1125,7 @@ public final class SimpleExpressionBuilder {
             public String toString() {
                 return "type:" + name;
             }
-        }
-
-        ;
+        };
     }
 
     /**
@@ -1197,8 +1195,8 @@ public final class SimpleExpressionBuilder {
         private Expression ognlExpression;
         private Language beanLanguage;
 
-        KeyedOgnlExpressionAdapter(String ognl, String toStringValue,
-                                   KeyedEntityRetrievalStrategy 
keyedEntityRetrievalStrategy) {
+        public KeyedOgnlExpressionAdapter(String ognl, String toStringValue,
+                                          KeyedEntityRetrievalStrategy 
keyedEntityRetrievalStrategy) {
             this.ognl = ognl;
             this.toStringValue = toStringValue;
             this.keyedEntityRetrievalStrategy = keyedEntityRetrievalStrategy;
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
index 56260a8b7b7..1ee19757064 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java
@@ -206,7 +206,7 @@ public class SimpleExpressionParser extends 
BaseSimpleParser {
         StringBuilder sb = new StringBuilder(256);
         boolean firstIsLiteral = false;
         for (SimpleNode node : nodes) {
-            String exp = node.createCode(expression);
+            String exp = node.createCode(camelContext, expression);
             if (exp != null) {
                 if (sb.isEmpty() && node instanceof LiteralNode) {
                     firstIsLiteral = true;
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
index 66eef5fd785..32386e86852 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java
@@ -165,7 +165,7 @@ public class SimplePredicateParser extends BaseSimpleParser 
{
     protected String doParseCode() {
         StringBuilder sb = new StringBuilder(256);
         for (SimpleNode node : nodes) {
-            String exp = node.createCode(expression);
+            String exp = node.createCode(camelContext, expression);
             SimpleExpressionParser.parseLiteralNode(sb, node, exp);
         }
         String code = sb.toString();
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java
index 374212071f3..ff77e75cb40 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java
@@ -16,6 +16,7 @@
  */
 package org.apache.camel.language.simple.ast;
 
+import org.apache.camel.CamelContext;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
 
@@ -40,10 +41,11 @@ public abstract class BaseSimpleNode implements SimpleNode {
         return token.getText();
     }
 
-    protected static String createCode(String expression, CompositeNodes 
block) throws SimpleParserException {
+    protected static String createCode(CamelContext camelContext, String 
expression, CompositeNodes block)
+            throws SimpleParserException {
         String answer = null;
         if (block != null) {
-            answer = block.createCode(expression);
+            answer = block.createCode(camelContext, expression);
         }
         // use double quote as this become used as string literals in the 
generated code
         if (answer == null) {
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
index e6349c533d7..64fec4a9d5b 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java
@@ -282,16 +282,16 @@ public class BinaryExpression extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
-        return BaseSimpleParser.CODE_START + doCreateCode(expression) + 
BaseSimpleParser.CODE_END;
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
     }
 
-    private String doCreateCode(String expression) throws 
SimpleParserException {
+    private String doCreateCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         org.apache.camel.util.ObjectHelper.notNull(left, "left node", this);
         org.apache.camel.util.ObjectHelper.notNull(right, "right node", this);
 
-        final String leftExp = left.createCode(expression);
-        final String rightExp = right.createCode(expression);
+        final String leftExp = left.createCode(camelContext, expression);
+        final String rightExp = right.createCode(camelContext, expression);
 
         if (operator == BinaryOperatorType.EQ) {
             return "isEqualTo(exchange, " + leftExp + ", " + rightExp + ")";
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java
index eec56f05212..ea26653b471 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java
@@ -53,7 +53,7 @@ public class BooleanExpression extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return value ? "true" : "false";
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java
index 3182e8b56fb..b7cf251c8dd 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java
@@ -69,15 +69,15 @@ public class CompositeNodes extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         if (children.isEmpty()) {
             return null;
         } else if (children.size() == 1) {
-            return children.get(0).createCode(expression);
+            return children.get(0).createCode(camelContext, expression);
         } else {
             StringBuilder sb = new StringBuilder(256);
             for (SimpleNode child : children) {
-                String code = child.createCode(expression);
+                String code = child.createCode(camelContext, expression);
                 if (code != null) {
                     sb.append(code);
                 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java
index 721652022d0..62e0e802b0e 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java
@@ -36,7 +36,7 @@ public class DoubleQuoteEnd extends BaseSimpleNode implements 
BlockEnd {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return null;
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java
index bd4428db5b6..1a35af02c39 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java
@@ -60,7 +60,7 @@ public class DoubleQuoteStart extends BaseSimpleNode 
implements BlockStart {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
-        return BaseSimpleNode.createCode(expression, block);
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleNode.createCode(camelContext, expression, block);
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
index 60167e82152..5c59c101e1a 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java
@@ -62,7 +62,7 @@ public class LiteralExpression extends BaseSimpleNode 
implements LiteralNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return getText();
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java
index 9fc06ecd133..afbcd2efec0 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java
@@ -123,16 +123,16 @@ public class LogicalExpression extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
-        return BaseSimpleParser.CODE_START + doCreateCode(expression) + 
BaseSimpleParser.CODE_END;
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
     }
 
-    private String doCreateCode(String expression) throws 
SimpleParserException {
+    private String doCreateCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         ObjectHelper.notNull(left, "left node", this);
         ObjectHelper.notNull(right, "right node", this);
 
-        final String leftExp = left.createCode(expression);
-        final String rightExp = right.createCode(expression);
+        final String leftExp = left.createCode(camelContext, expression);
+        final String rightExp = right.createCode(camelContext, expression);
 
         if (operator == LogicalOperatorType.AND) {
             return leftExp + " && " + rightExp;
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java
index 46fb25dec4b..00faa5cff1a 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java
@@ -49,7 +49,7 @@ public class NullExpression extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return "null";
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java
index ceda57ef05b..9f39a63e4f4 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java
@@ -72,7 +72,7 @@ public class NumericExpression extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         // Double, Long or Integer
         if (number instanceof Double) {
             return number + "d";
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java
index 5dac2d21c4e..bde21cff2a9 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java
@@ -36,7 +36,7 @@ public class SimpleFunctionEnd extends BaseSimpleNode 
implements BlockEnd {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return null;
     }
 }
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 3c2a07311c9..55c6701ce6d 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
@@ -20,6 +20,7 @@ import java.net.URISyntaxException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Expression;
@@ -29,6 +30,8 @@ import 
org.apache.camel.language.simple.SimpleExpressionBuilder;
 import org.apache.camel.language.simple.types.SimpleParserException;
 import org.apache.camel.language.simple.types.SimpleToken;
 import org.apache.camel.spi.Language;
+import org.apache.camel.spi.SimpleLanguageFunctionFactory;
+import org.apache.camel.support.ResolverHelper;
 import org.apache.camel.support.builder.ExpressionBuilder;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.util.OgnlHelper;
@@ -325,6 +328,14 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return misc;
         }
 
+        // attachments
+        if ("attachments".equals(function) || 
ifStartsWithReturnRemainder("attachment", function) != null) {
+            Expression exp = createSimpleAttachments(camelContext, function);
+            if (exp != null) {
+                return exp;
+            }
+        }
+
         if (strict) {
             throw new SimpleParserException("Unknown function: " + function, 
token.getIndex());
         } else {
@@ -332,6 +343,20 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         }
     }
 
+    private Expression createSimpleAttachments(CamelContext camelContext, 
String function) {
+        Optional<SimpleLanguageFunctionFactory> factory = 
ResolverHelper.resolveService(
+                camelContext,
+                
camelContext.getCamelContextExtension().getBootstrapFactoryFinder(),
+                SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments",
+                SimpleLanguageFunctionFactory.class);
+
+        if (factory.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Cannot find SimpleLanguageFunctionFactory on classpath. 
Add camel-attachments to classpath.");
+        }
+        return factory.get().createFunction(camelContext, function, 
token.getIndex());
+    }
+
     private Expression createSimpleExpressionMessage(CamelContext 
camelContext, String function, boolean strict) {
         // messageAs
         String remainder = ifStartsWithReturnRemainder("messageAs(", function);
@@ -435,6 +460,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         // headers function
         if ("in.headers".equals(function) || "headers".equals(function)) {
             return ExpressionBuilder.headersExpression();
+        } else if ("headers.size".equals(function) || 
"headers.size()".equals(function)
+                || "headers.length".equals(function) || 
"headers.length()".equals(function)) {
+            return ExpressionBuilder.headersSizeExpression();
         }
 
         // in header function
@@ -492,6 +520,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         // variables function
         if ("variables".equals(function)) {
             return ExpressionBuilder.variablesExpression();
+        } else if ("variables.size".equals(function) || 
"variables.size()".equals(function)
+                || "variables.length".equals(function) || 
"variables.length()".equals(function)) {
+            return ExpressionBuilder.variablesSizeExpression();
         }
 
         // variable function
@@ -874,11 +905,11 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
-        return BaseSimpleParser.CODE_START + doCreateCode(expression) + 
BaseSimpleParser.CODE_END;
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
     }
 
-    private String doCreateCode(String expression) throws 
SimpleParserException {
+    private String doCreateCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         String function = getText();
 
         // return the function directly if we can create function without 
analyzing the prefix
@@ -1090,6 +1121,14 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
             return misc;
         }
 
+        // attachments
+        if ("attachments".equals(function) || 
ifStartsWithReturnRemainder("attachment", function) != null) {
+            String code = createCodeAttachments(camelContext, function);
+            if (code != null) {
+                return code;
+            }
+        }
+
         throw new SimpleParserException("Unknown function: " + function, 
token.getIndex());
     }
 
@@ -1373,6 +1412,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         // headers function
         if ("in.headers".equals(function) || "headers".equals(function)) {
             return "message.getHeaders()";
+        } else if ("headers.size".equals(function) || 
"headers.size()".equals(function)
+                || "headers.length".equals(function) || 
"headers.length()".equals(function)) {
+            return "message.getHeaders().size()";
         }
 
         // in header function
@@ -1485,6 +1527,9 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         // variables function
         if ("variables".equals(function)) {
             return "variables(exchange)";
+        } else if ("variables.size".equals(function) || 
"variables.size()".equals(function)
+                || "variables.length".equals(function) || 
"variables.length()".equals(function)) {
+            return "variablesSize(exchange)";
         }
 
         // variable
@@ -1687,6 +1732,20 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
         throw new SimpleParserException("Unknown file language syntax: " + 
remainder, token.getIndex());
     }
 
+    private String createCodeAttachments(CamelContext camelContext, String 
function) {
+        Optional<SimpleLanguageFunctionFactory> factory = 
ResolverHelper.resolveService(
+                camelContext,
+                
camelContext.getCamelContextExtension().getBootstrapFactoryFinder(),
+                SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments",
+                SimpleLanguageFunctionFactory.class);
+
+        if (factory.isEmpty()) {
+            throw new IllegalArgumentException(
+                    "Cannot find SimpleLanguageFunctionFactory on classpath. 
Add camel-attachments to classpath.");
+        }
+        return factory.get().createCode(camelContext, function, 
token.getIndex());
+    }
+
     private String createCodeExpressionMisc(String function) {
         String remainder;
 
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
index 68e8950a730..852a6d64db2 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java
@@ -149,25 +149,25 @@ public class SimpleFunctionStart extends BaseSimpleNode 
implements BlockStart {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         String answer;
         // a function can either be a simple literal function or contain 
nested functions
         if (block.getChildren().size() == 1 && block.getChildren().get(0) 
instanceof LiteralNode) {
-            answer = doCreateLiteralCode(expression);
+            answer = doCreateLiteralCode(camelContext, expression);
         } else {
-            answer = doCreateCompositeCode(expression);
+            answer = doCreateCompositeCode(camelContext, expression);
         }
         return answer;
     }
 
-    private String doCreateLiteralCode(String expression) {
+    private String doCreateLiteralCode(CamelContext camelContext, String 
expression) {
         SimpleFunctionExpression function = new 
SimpleFunctionExpression(this.getToken(), cacheExpression);
         LiteralNode literal = (LiteralNode) block.getChildren().get(0);
         function.addText(literal.getText());
-        return function.createCode(expression);
+        return function.createCode(camelContext, expression);
     }
 
-    private String doCreateCompositeCode(String expression) {
+    private String doCreateCompositeCode(CamelContext camelContext, String 
expression) {
         StringBuilder sb = new StringBuilder(256);
         boolean quoteEmbeddedFunctions = false;
 
@@ -181,7 +181,7 @@ public class SimpleFunctionStart extends BaseSimpleNode 
implements BlockStart {
             } else if (child instanceof SingleQuoteStart || child instanceof 
DoubleQuoteStart) {
                 try {
                     // pass in null when we evaluate the nested expressions
-                    String text = child.createCode(null);
+                    String text = child.createCode(camelContext, null);
                     if (text != null) {
                         if (quoteEmbeddedFunctions && 
!StringHelper.isQuoted(text)) {
                             sb.append("'").append(text).append("'");
@@ -195,7 +195,7 @@ public class SimpleFunctionStart extends BaseSimpleNode 
implements BlockStart {
                 }
             } else if (child instanceof SimpleFunctionStart) {
                 // inlined function
-                String inlined = child.createCode(expression);
+                String inlined = child.createCode(camelContext, expression);
                 sb.append(inlined);
             }
         }
@@ -206,7 +206,7 @@ public class SimpleFunctionStart extends BaseSimpleNode 
implements BlockStart {
         SimpleFunctionExpression function = new 
SimpleFunctionExpression(token, cacheExpression);
         function.addText(exp);
         try {
-            return function.createCode(exp);
+            return function.createCode(camelContext, exp);
         } catch (SimpleParserException e) {
             // must rethrow parser exception as illegal syntax with details 
about the location
             throw new SimpleIllegalSyntaxException(expression, e.getIndex(), 
e.getMessage(), e);
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java
index ed79da3df38..559af1791b6 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java
@@ -46,10 +46,11 @@ public interface SimpleNode {
     /**
      * Creates Java code based on this model.
      *
+     * @param  camelContext                                                 
the camel context
      * @param  expression                                                   
the input string
      * @return                                                              
the created Java code
      * @throws org.apache.camel.language.simple.types.SimpleParserException 
should be thrown if error parsing the model
      */
-    String createCode(String expression) throws SimpleParserException;
+    String createCode(CamelContext camelContext, String expression) throws 
SimpleParserException;
 
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java
index 3e5b97f6734..081edaa09ee 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java
@@ -36,7 +36,7 @@ public class SingleQuoteEnd extends BaseSimpleNode implements 
BlockEnd {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         return null;
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java
index ce23e4246d6..a7a907ec07f 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java
@@ -64,7 +64,7 @@ public class SingleQuoteStart extends BaseSimpleNode 
implements BlockStart {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
-        return BaseSimpleNode.createCode(expression, block);
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleNode.createCode(camelContext, expression, block);
     }
 }
diff --git 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
index e39ab05d16c..0aeb332caa0 100644
--- 
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
+++ 
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java
@@ -147,14 +147,14 @@ public class UnaryExpression extends BaseSimpleNode {
     }
 
     @Override
-    public String createCode(String expression) throws SimpleParserException {
-        return BaseSimpleParser.CODE_START + doCreateCode(expression) + 
BaseSimpleParser.CODE_END;
+    public String createCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
+        return BaseSimpleParser.CODE_START + doCreateCode(camelContext, 
expression) + BaseSimpleParser.CODE_END;
     }
 
-    private String doCreateCode(String expression) throws 
SimpleParserException {
+    private String doCreateCode(CamelContext camelContext, String expression) 
throws SimpleParserException {
         ObjectHelper.notNull(left, "left node", this);
 
-        final String number = left.createCode(expression);
+        final String number = left.createCode(camelContext, expression);
 
         if (operator == UnaryOperatorType.INC) {
             return "increment(exchange, " + number + ")";
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index ae9bdc6147d..fbcc49b19a1 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -889,6 +889,7 @@ public class SimpleTest extends LanguageTestSupport {
 
         assertExpression("${headers}", headers);
         assertExpression("${in.headers}", headers);
+        assertExpression("${headers.size}", 2);
     }
 
     @Test
@@ -952,6 +953,7 @@ public class SimpleTest extends LanguageTestSupport {
         assertEquals(3, variables.size());
 
         assertExpression("${variables}", variables);
+        assertExpression("${variables.size}", 3);
     }
 
     @Test
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 8508272a029..8181e3b0e7c 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
@@ -367,6 +367,23 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an expression for the size of the message headers
+     */
+    public static Expression headersSizeExpression() {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                return exchange.getIn().getHeaders().size();
+            }
+
+            @Override
+            public String toString() {
+                return "headers.size";
+            }
+        };
+    }
+
     /**
      * Returns an expression for the {@link Exchange} variables
      *
@@ -386,6 +403,23 @@ public class ExpressionBuilder {
         };
     }
 
+    /**
+     * Returns an expression for the number of {@link Exchange} variables
+     */
+    public static Expression variablesSizeExpression() {
+        return new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                return exchange.getVariables().size();
+            }
+
+            @Override
+            public String toString() {
+                return "variables.size";
+            }
+        };
+    }
+
     /**
      * Returns an expression for the exchange pattern
      *

Reply via email to