This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch cf in repository https://gitbox.apache.org/repos/asf/camel.git
commit 533423bfbb9e7ae17653009140d86425aee054cb Author: Claus Ibsen <[email protected]> AuthorDate: Wed Feb 4 21:38:36 2026 +0100 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..a5382e1bf07d 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 and classpath) * * @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; + } + } }
