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;
+ }
+ }
}