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

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


The following commit(s) were added to refs/heads/main by this push:
     new 200bd04faecf CAMEL-22935: camel-core - Allow to add custom functions 
to simple language (#21250)
200bd04faecf is described below

commit 200bd04faecfacf442b629632651d1b13d7b56d5
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Feb 5 08:25:11 2026 +0100

    CAMEL-22935: camel-core - Allow to add custom functions to simple language 
(#21250)
    
    * CAMEL-22935: camel-core - Allow to add custom functions to simple language
---
 .../catalog/dev-consoles/simple-language.json      |  2 +-
 .../java/org/apache/camel/spi/SimpleFunction.java  | 54 +++++++++++++++++++
 .../apache/camel/spi/SimpleFunctionRegistry.java   | 13 +++--
 .../impl/engine/DefaultSimpleFunctionRegistry.java | 61 ++++++++++++++++++++--
 .../apache/camel/dev-console/simple-language.json  |  2 +-
 .../org/apache/camel/dev-console/simple-language   |  2 +-
 ...eConsole.java => SimpleLanguageDevConsole.java} |  4 +-
 .../modules/languages/pages/simple-language.adoc   | 36 +++++++++++++
 .../language/simple/SimpleExpressionBuilder.java   |  8 +--
 .../simple/ast/SimpleFunctionExpression.java       |  2 +-
 .../language/simple/SimpleCustomFunctionTest.java  | 23 +++++---
 11 files changed, 182 insertions(+), 25 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/simple-language.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/simple-language.json
index 574bed5374d6..9f52d619f8b0 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/simple-language.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/dev-consoles/simple-language.json
@@ -6,7 +6,7 @@
     "title": "Simple Language",
     "description": "Display simple language details",
     "deprecated": false,
-    "javaType": "org.apache.camel.impl.console.SimpleLanguageConsole",
+    "javaType": "org.apache.camel.impl.console.SimpleLanguageDevConsole",
     "groupId": "org.apache.camel",
     "artifactId": "camel-console",
     "version": "4.18.0-SNAPSHOT"
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunction.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunction.java
new file mode 100644
index 000000000000..fc7ef152ac21
--- /dev/null
+++ b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunction.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.spi;
+
+import org.apache.camel.Exchange;
+
+/**
+ * A custom simple language function
+ *
+ * This allows to plugin custom functions to the built-in simple language.
+ */
+public interface SimpleFunction {
+
+    /**
+     * The name of the function.
+     *
+     * Notice the name must not clash with any of the built-in function names.
+     */
+    String getName();
+
+    /**
+     * Applies the function to the given input
+     *
+     * @param  exchange  the current exchange
+     * @param  input     the input object, can be null
+     * @return           the response
+     * @throws Exception can be thrown if there was an error
+     */
+    Object apply(Exchange exchange, Object input) throws Exception;
+
+    /**
+     * Whether this custom function allows null as input value.
+     *
+     * This is default false to avoid {@link NullPointerException} and how the 
built-in functions also behaves.
+     */
+    default boolean allowNull() {
+        return false;
+    }
+
+}
diff --git 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
index bd5538e017fc..0abf69d2cee4 100644
--- 
a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
+++ 
b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleFunctionRegistry.java
@@ -34,6 +34,13 @@ public interface SimpleFunctionRegistry extends 
StaticService {
      */
     void addFunction(String name, Expression expression);
 
+    /**
+     * Add a custom simple function
+     *
+     * @param function the function to add
+     */
+    void addFunction(SimpleFunction function);
+
     /**
      * Remove a custom simple function
      *
@@ -42,7 +49,7 @@ public interface SimpleFunctionRegistry extends StaticService 
{
     void removeFunction(String name);
 
     /**
-     * Gets the function
+     * Gets the function (will resolve custom functions from registry)
      *
      * @param  name name of function
      * @return      the function, or <tt>null</tt> if no function exists
@@ -50,7 +57,7 @@ public interface SimpleFunctionRegistry extends StaticService 
{
     Expression getFunction(String name);
 
     /**
-     * Returns a set with all the custom function names
+     * Returns a set with all the custom function names currently in use
      */
     Set<String> getCustomFunctionNames();
 
@@ -60,7 +67,7 @@ public interface SimpleFunctionRegistry extends StaticService 
{
     Set<String> getCoreFunctionNames();
 
     /**
-     * Number of custom functions
+     * Number of custom functions currently in use
      */
     int customSize();
 
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
index f874c19fa389..0cbb5d553580 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultSimpleFunctionRegistry.java
@@ -22,19 +22,27 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.Exchange;
 import org.apache.camel.Expression;
+import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.StaticService;
+import org.apache.camel.spi.SimpleFunction;
 import org.apache.camel.spi.SimpleFunctionRegistry;
+import org.apache.camel.support.ExpressionAdapter;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.SimpleUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Default {@link SimpleFunctionRegistry}.
  */
 public class DefaultSimpleFunctionRegistry extends ServiceSupport implements 
SimpleFunctionRegistry, StaticService {
 
-    private final Map<String, Expression> functions = new 
ConcurrentHashMap<>();
+    private static final Logger LOG = 
LoggerFactory.getLogger(DefaultSimpleFunctionRegistry.class);
+
     private final CamelContext camelContext;
+    private final Map<String, Expression> functions = new 
ConcurrentHashMap<>();
 
     public DefaultSimpleFunctionRegistry(CamelContext camelContext) {
         this.camelContext = camelContext;
@@ -42,6 +50,8 @@ public class DefaultSimpleFunctionRegistry extends 
ServiceSupport implements Sim
 
     @Override
     public void addFunction(String name, Expression expression) {
+        LOG.debug("Adding simple custom function: {}", name);
+
         String lower = name.toLowerCase(Locale.ENGLISH);
         if (SimpleUtils.getFunctions().contains(lower)) {
             throw new IllegalArgumentException("Simple already have built-in 
function with name: " + name);
@@ -50,6 +60,39 @@ public class DefaultSimpleFunctionRegistry extends 
ServiceSupport implements Sim
         functions.put(name, expression);
     }
 
+    @Override
+    public void addFunction(SimpleFunction function) {
+        LOG.debug("Adding simple custom function: {}", function.getName());
+
+        String lower = function.getName().toLowerCase(Locale.ENGLISH);
+        if (SimpleUtils.getFunctions().contains(lower)) {
+            throw new IllegalArgumentException("Simple already have built-in 
function with name: " + function.getName());
+        }
+
+        ExpressionAdapter adapter = new ExpressionAdapter() {
+            @Override
+            public Object evaluate(Exchange exchange) {
+                Object body = exchange.getMessage().getBody();
+                if (body == null && !function.allowNull()) {
+                    return null;
+                }
+                try {
+                    return function.apply(exchange, body);
+                } catch (Exception e) {
+                    throw RuntimeCamelException.wrapRuntimeException(e);
+                }
+            }
+
+            @Override
+            public String toString() {
+                return "function(" + function.getName() + ")";
+            }
+        };
+        adapter.init(camelContext);
+
+        functions.put(function.getName(), adapter);
+    }
+
     @Override
     public void removeFunction(String name) {
         functions.remove(name);
@@ -57,7 +100,19 @@ public class DefaultSimpleFunctionRegistry extends 
ServiceSupport implements Sim
 
     @Override
     public Expression getFunction(String name) {
-        return functions.get(name);
+        Expression exp = functions.get(name);
+        if (exp == null) {
+            // lookup if there is a function with the given name
+            var custom = 
camelContext.getRegistry().findByType(SimpleFunction.class);
+            for (SimpleFunction sf : custom) {
+                if (name.equals(sf.getName())) {
+                    addFunction(sf);
+                    exp = functions.get(name);
+                    break;
+                }
+            }
+        }
+        return exp;
     }
 
     @Override
@@ -82,7 +137,7 @@ public class DefaultSimpleFunctionRegistry extends 
ServiceSupport implements Sim
 
     @Override
     protected void doStop() throws Exception {
-        super.doShutdown();
+        super.doStop();
         functions.clear();
     }
 }
diff --git 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/simple-language.json
 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/simple-language.json
index 574bed5374d6..9f52d619f8b0 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/simple-language.json
+++ 
b/core/camel-console/src/generated/resources/META-INF/org/apache/camel/dev-console/simple-language.json
@@ -6,7 +6,7 @@
     "title": "Simple Language",
     "description": "Display simple language details",
     "deprecated": false,
-    "javaType": "org.apache.camel.impl.console.SimpleLanguageConsole",
+    "javaType": "org.apache.camel.impl.console.SimpleLanguageDevConsole",
     "groupId": "org.apache.camel",
     "artifactId": "camel-console",
     "version": "4.18.0-SNAPSHOT"
diff --git 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/simple-language
 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/simple-language
index 347f59529c4f..bb7872cd8da4 100644
--- 
a/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/simple-language
+++ 
b/core/camel-console/src/generated/resources/META-INF/services/org/apache/camel/dev-console/simple-language
@@ -1,2 +1,2 @@
 # Generated by camel build tools - do NOT edit this file!
-class=org.apache.camel.impl.console.SimpleLanguageConsole
+class=org.apache.camel.impl.console.SimpleLanguageDevConsole
diff --git 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageConsole.java
 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java
similarity index 96%
rename from 
core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageConsole.java
rename to 
core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java
index 39dcfcfce097..517c1eaea2fb 100644
--- 
a/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageConsole.java
+++ 
b/core/camel-console/src/main/java/org/apache/camel/impl/console/SimpleLanguageDevConsole.java
@@ -26,9 +26,9 @@ import org.apache.camel.util.json.JsonArray;
 import org.apache.camel.util.json.JsonObject;
 
 @DevConsole(name = "simple-language", displayName = "Simple Language", 
description = "Display simple language details")
-public class SimpleLanguageConsole extends AbstractDevConsole {
+public class SimpleLanguageDevConsole extends AbstractDevConsole {
 
-    public SimpleLanguageConsole() {
+    public SimpleLanguageDevConsole() {
         super("camel", "simple-language", "Simple Language", "Display simple 
language details");
     }
 
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 f9a5ed9e0ceb..c21550d979da 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
@@ -1844,6 +1844,9 @@ And in XML DSL you use the pretty attribute to true as 
show below:
 
 You can add custom functions to the Simple language by adding to the 
`org.apache.camel.spi.SimpleFunctionRegistry`.
 
+A custom function should either be an `Expression` or a 
`org.apache.camel.spi.SimpleFunction` implementation.
+
+You may want to use `Expression` when you build custom functions from the 
built-in functions in Camel.
 This can be done programmatically such as:
 
 [source,java]
@@ -1906,6 +1909,39 @@ from("direct:start")
         .to("mock:result");
 ----
 
+You can also build custom functions as a Java class as below:
+
+[source,java]
+----
+public class FooSimpleFunction implements SimpleFunction {
+
+    @Override
+    public String getName() {
+        return "foo";
+    }
+
+    @Override
+    public Object apply(Exchange exchange, Object input) throws Exception {
+        return "I was here " + input;
+    }
+}
+----
+
+This gives you the full power to implement the function logic in standard Java.
+
+This function can be added programmatically:
+
+[source,java]
+----
+SimpleFunctionRegistry reg = 
PluginHelper.getSimpleFunctionRegistry(getCamelContext());
+
+reg.addFunction(new FooSimpleFunction());
+----
+
+TIP: The custom function can then be made discoverable by Camel by dependency 
injection.
+If you use standalone Camel you can add `@BindToRegistry("foo-function)` to 
the class.
+For Spring Boot use `@Component` or `@Service` and Quarkus you can for example 
use `@ApplicationScoped`.
+
 
 == Dependencies
 
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 ecd48ff2acdb..9fd33f562017 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
@@ -3080,18 +3080,14 @@ public final class SimpleExpressionBuilder {
 
             @Override
             public Object evaluate(Exchange exchange) {
-                Object answer = null;
                 final Object originalBody = exchange.getMessage().getBody();
                 try {
                     Object input = exp.evaluate(exchange, Object.class);
-                    if (input != null) {
-                        exchange.getMessage().setBody(input);
-                        answer = func.evaluate(exchange, Object.class);
-                    }
+                    exchange.getMessage().setBody(input);
+                    return func.evaluate(exchange, Object.class);
                 } finally {
                     exchange.getMessage().setBody(originalBody);
                 }
-                return answer;
             }
 
             public String toString() {
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 185cdaeb718f..c5333495c46f 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
@@ -373,7 +373,7 @@ public class SimpleFunctionExpression extends 
LiteralExpression {
 
         // it may be a custom function
         String name = StringHelper.before(function, "(", function);
-        if 
(PluginHelper.getSimpleFunctionRegistry(camelContext).getCustomFunctionNames().contains(name))
 {
+        if 
(PluginHelper.getSimpleFunctionRegistry(camelContext).getFunction(name) != 
null) {
             String after = StringHelper.after(function, "(");
             if (after == null || after.equals(")")) {
                 function = "function(" + name + ")";
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleCustomFunctionTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleCustomFunctionTest.java
index 51e87b817b9f..331f69cfc5bd 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleCustomFunctionTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleCustomFunctionTest.java
@@ -20,8 +20,8 @@ import org.apache.camel.ContextTestSupport;
 import org.apache.camel.Exchange;
 import org.apache.camel.RoutesBuilder;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.spi.SimpleFunction;
 import org.apache.camel.spi.SimpleFunctionRegistry;
-import org.apache.camel.support.ExpressionAdapter;
 import org.apache.camel.support.PluginHelper;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -84,12 +84,8 @@ public class SimpleCustomFunctionTest extends 
ContextTestSupport {
             @Override
             public void configure() throws Exception {
                 SimpleFunctionRegistry reg = 
PluginHelper.getSimpleFunctionRegistry(getCamelContext());
-                reg.addFunction("foo", new ExpressionAdapter() {
-                    @Override
-                    public Object evaluate(Exchange exchange) {
-                        return "I was here " + exchange.getMessage().getBody();
-                    }
-                });
+
+                reg.addFunction(new FooSimpleFunction());
 
                 var bar = context.resolveLanguage("simple")
                         .createExpression("${trim()} ~> 
${normalizeWhitespace()} ~> ${capitalize()} ~> ${quote()}");
@@ -119,4 +115,17 @@ public class SimpleCustomFunctionTest extends 
ContextTestSupport {
             }
         };
     }
+
+    private static class FooSimpleFunction implements SimpleFunction {
+
+        @Override
+        public String getName() {
+            return "foo";
+        }
+
+        @Override
+        public Object apply(Exchange exchange, Object input) throws Exception {
+            return "I was here " + input;
+        }
+    }
 }

Reply via email to