Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -42,15 +49,15 @@ 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
*/
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();

Expand All @@ -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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,36 @@
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;
}

@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);
Expand All @@ -50,14 +60,59 @@ public void addFunction(String name, Expression expression) {
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);
}

@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
Expand All @@ -82,7 +137,7 @@ public int coreSize() {

@Override
protected void doStop() throws Exception {
super.doShutdown();
super.doStop();
functions.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@
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");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3080,18 +3080,14 @@ public void init(CamelContext context) {

@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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ private Expression createSimpleExpression(CamelContext camelContext, String func

// 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 + ")";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
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;
Expand Down Expand Up @@ -84,12 +84,8 @@ protected RoutesBuilder createRouteBuilder() throws Exception {
@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()}");
Expand Down Expand Up @@ -119,4 +115,17 @@ public Object evaluate(Exchange exchange) {
}
};
}

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