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 10490761ffc CAMEL-21081: Add iif function to simple language (#15168)
10490761ffc is described below
commit 10490761ffcb1601985e59912eb9d11cf7183800
Author: Claus Ibsen <[email protected]>
AuthorDate: Thu Aug 15 10:11:36 2024 +0200
CAMEL-21081: Add iif function to simple language (#15168)
CAMEL-21081: Add iif function to simple and csimple language
CAMEL-21086: Add more complex functions to csimple language
---------
Co-authored-by: Adriano Machado <[email protected]>
---
.../src/main/docs/csimple-joor.adoc | 3 -
.../language/csimple/joor/OriginalSimpleTest.java | 126 ++++++++-
.../modules/languages/pages/simple-language.adoc | 3 +
.../language/csimple/CSimpleCodeGenerator.java | 13 +-
.../camel/language/csimple/CSimpleHelper.java | 67 ++++-
.../camel/language/simple/BaseSimpleParser.java | 12 +-
.../language/simple/SimpleExpressionBuilder.java | 40 ++-
.../language/simple/SimpleExpressionParser.java | 17 +-
.../language/simple/SimplePredicateParser.java | 5 +-
.../language/simple/ast/BinaryExpression.java | 5 +
.../language/simple/ast/LogicalExpression.java | 5 +
.../simple/ast/SimpleFunctionExpression.java | 299 ++++++++++++++++++++-
.../language/simple/ast/SimpleFunctionStart.java | 2 +-
.../camel/language/simple/ast/UnaryExpression.java | 5 +
.../apache/camel/language/simple/SimpleTest.java | 15 ++
15 files changed, 582 insertions(+), 35 deletions(-)
diff --git a/components/camel-csimple-joor/src/main/docs/csimple-joor.adoc
b/components/camel-csimple-joor/src/main/docs/csimple-joor.adoc
index f3424d9e1ad..168a531d290 100644
--- a/components/camel-csimple-joor/src/main/docs/csimple-joor.adoc
+++ b/components/camel-csimple-joor/src/main/docs/csimple-joor.adoc
@@ -18,9 +18,6 @@ This module includes the jOOR compiler for the CSimple
language for runtime comp
To use this, just include `camel-csimple-joor` in the classpath.
-NOTE: Java 8 is not supported. Java 11 or newer is required.
-
-
== Limitations
The supported runtime is intended for Java standalone, Spring Boot, Camel
Quarkus and other microservices runtimes.
diff --git
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
index 4c55bfb9b0b..c3b2d990983 100644
---
a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
+++
b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/OriginalSimpleTest.java
@@ -16,6 +16,8 @@
*/
package org.apache.camel.language.csimple.joor;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
@@ -41,11 +43,13 @@ import
org.apache.camel.language.bean.RuntimeBeanExpressionException;
import org.apache.camel.language.csimple.CSimpleLanguage;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
import org.apache.camel.spi.Language;
+import org.apache.camel.spi.UuidGenerator;
import org.apache.camel.spi.VariableRepository;
import org.apache.camel.spi.VariableRepositoryFactory;
import org.apache.camel.support.ExchangeHelper;
import org.apache.camel.test.junit5.LanguageTestSupport;
import org.apache.camel.util.InetAddressUtil;
+import org.apache.camel.util.StringHelper;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@@ -63,11 +67,13 @@ public class OriginalSimpleTest extends LanguageTestSupport
{
private static final String INDEX_OUT_OF_BOUNDS_ERROR_MSG = "Index 2 out
of bounds for length 2";
+ @SuppressWarnings("unused")
@BindToRegistry
- private Animal myAnimal = new Animal("Donkey", 17);
+ private final Animal myAnimal = new Animal("Donkey", 17);
+ @SuppressWarnings("unused")
@BindToRegistry
- private Greeter greeter = new Greeter();
+ private final Greeter greeter = new Greeter();
@Override
protected CamelContext createCamelContext() throws Exception {
@@ -2086,6 +2092,122 @@ public class OriginalSimpleTest extends
LanguageTestSupport {
}
}
+ @Test
+ public void testIif() {
+ exchange.getIn().setHeader("foo", 44);
+ assertExpression("${iif(${headerAs(foo,int)} >
0,'positive','negative')}", "positive");
+ exchange.getIn().setHeader("foo", -123);
+ assertExpression("${iif(${headerAs(foo,int)} >
0,'positive','negative')}", "negative");
+
+ exchange.getIn().setBody("Hello World");
+ exchange.getIn().setHeader("foo", 44);
+ assertExpression("${iif(${headerAs(foo,int)} > 0,${body},'Bye
World')}", "Hello World");
+ exchange.getIn().setHeader("foo", -123);
+ assertExpression("${iif(${headerAs(foo,int)} > 0,${body},'Bye
World')}", "Bye World");
+ assertExpression("${iif(${headerAs(foo,int)} > 0,${body},${null})}",
null);
+
+ exchange.setVariable("cheese", "gauda");
+ exchange.getMessage().setHeader("counter", 3);
+ assertExpression("${iif(true, ${variableAs('cheese', 'String')},
${headerAs('counter', 'Integer')})}", "gauda");
+ assertExpression("${iif(false, ${variableAs('cheese', 'String')},
${headerAs('counter', 'Integer')})}", "3");
+ }
+
+ @Test
+ public void testJoinBody() {
+ List<Object> data = new ArrayList<>();
+ data.add("A");
+ data.add("B");
+ data.add("C");
+ exchange.getIn().setBody(data);
+
+ assertExpression("${join()}", "A,B,C");
+ assertExpression("${join(;)}", "A;B;C");
+ assertExpression("${join(' ')}", "A B C");
+ assertExpression("${join(',','id=')}", "id=A,id=B,id=C");
+ assertExpression("${join(&,id=)}", "id=A&id=B&id=C");
+ }
+
+ @Test
+ public void testJoinHeader() {
+ List<Object> data = new ArrayList<>();
+ data.add("A");
+ data.add("B");
+ data.add("C");
+ exchange.getIn().setHeader("id", data);
+
+ assertExpression("${join('&','id=',${header.id})}", "id=A&id=B&id=C");
+ }
+
+ @Test
+ public void testHash() throws Exception {
+ Expression expression =
context.resolveLanguage("csimple").createExpression("${hash('hello')}");
+ String s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+ MessageDigest digest = MessageDigest.getInstance("SHA-256");
+ byte[] bytes = digest.digest("hello".getBytes(StandardCharsets.UTF_8));
+ String expected = StringHelper.bytesToHex(bytes);
+ assertEquals(expected, s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${hash(${body})}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+ digest = MessageDigest.getInstance("SHA-256");
+ bytes =
digest.digest(exchange.getMessage().getBody(String.class).getBytes(StandardCharsets.UTF_8));
+ expected = StringHelper.bytesToHex(bytes);
+ assertEquals(expected, s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${hash(${header.foo})}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${hash('hello',SHA3-256)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${hash(${body},SHA3-256)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+ digest = MessageDigest.getInstance("SHA3-256");
+ bytes =
digest.digest(exchange.getMessage().getBody(String.class).getBytes(StandardCharsets.UTF_8));
+ expected = StringHelper.bytesToHex(bytes);
+ assertEquals(expected, s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${hash(${header.foo},SHA3-256)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${hash(${header.unknown})}");
+ s = expression.evaluate(exchange, String.class);
+ assertNull(s);
+ }
+
+ @Test
+ public void testUuid() {
+ Expression expression =
context.resolveLanguage("csimple").createExpression("${uuid}");
+ String s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${uuid(default)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${uuid(short)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${uuid(simple)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ expression =
context.resolveLanguage("csimple").createExpression("${uuid(classic)}");
+ s = expression.evaluate(exchange, String.class);
+ assertNotNull(s);
+
+ // custom generator
+ context.getRegistry().bind("mygen", (UuidGenerator) () -> "1234");
+ assertExpression("${uuid(mygen)}", "1234");
+ }
+
@Override
protected String getLanguageName() {
return "csimple";
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 f39955569da..ac47f5fafee 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
@@ -314,6 +314,9 @@ For _input_ you can choose `header:key`,
`exchangeProperty:key` or `variable:key
|pretty(exp) | String | Converts the inlined expression to a String, and
attempts to pretty print if JSon or XML, otherwise the expression is returned
as the String value.
+|iif(predicate, trueExp, falseExp) | Object | Evaluates the `predicate`
expression and returns the value of `trueExp` if the predicate is
+true, otherwise the value of `falseExp` is returned. This function is similar
to the ternary operator in Java.
+
|=======================================================================
== OGNL expression support
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 5f263650f97..f6304517930 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
@@ -60,7 +60,7 @@ public class CSimpleCodeGenerator {
private CSimpleGeneratedCode generateCode(String fqn, String script,
boolean predicate) {
String text = script;
- // text should be single line and trimmed as it can be multi lined
+ // text should be single line and trimmed as it can be multi-lined
text = text.replaceAll("\n", "");
text = text.trim();
@@ -96,6 +96,7 @@ public class CSimpleCodeGenerator {
sb.append("public class ").append(name).append(" extends
org.apache.camel.language.csimple.CSimpleSupport {\n");
sb.append("\n");
sb.append(" Language bean;\n");
+ sb.append(" UuidGenerator uuid;\n");
sb.append("\n");
sb.append(" public ").append(name).append("() {\n");
sb.append(" }\n");
@@ -111,7 +112,7 @@ public class CSimpleCodeGenerator {
sb.append(" public String getText() {\n");
// \ should be escaped
String escaped = text.replace("\\", "\\\\");
- // we need to escape all " so its a single literal string
+ // we need to escape all " so it's a single literal string
escaped = escaped.replace("\"", "\\\"");
sb.append(" return \"").append(escaped).append("\";\n");
sb.append(" }\n");
@@ -121,9 +122,6 @@ public class CSimpleCodeGenerator {
sb.append(
" public Object evaluate(CamelContext context, Exchange
exchange, Message message, Object body) throws Exception {\n");
sb.append(" ");
- if (!script.contains("return ")) {
- sb.append("return ");
- }
if (predicate) {
CSimplePredicateParser parser = new CSimplePredicateParser();
@@ -136,11 +134,14 @@ public class CSimpleCodeGenerator {
CSimpleExpressionParser parser = new CSimpleExpressionParser();
script = parser.parseExpression(script);
if (script.isBlank()) {
- // an expression can be whitespace but then we need to wrap
this in quotes
+ // an expression can be whitespace, but then we need to wrap
this in quotes
script = "\"" + script + "\"";
}
}
+ if (!script.contains("return ")) {
+ sb.append("return ");
+ }
sb.append(script);
if (!script.endsWith("}") && !script.endsWith(";")) {
sb.append(";");
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 45dad64ea19..b572aef5478 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
@@ -17,6 +17,7 @@
package org.apache.camel.language.csimple;
import java.lang.reflect.Array;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
@@ -29,6 +30,7 @@ import java.util.regex.Pattern;
import org.apache.camel.CamelContext;
import org.apache.camel.CamelExchangeException;
+import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePropertyKey;
import org.apache.camel.Expression;
@@ -39,10 +41,16 @@ import org.apache.camel.RuntimeCamelException;
import org.apache.camel.spi.ExchangeFormatter;
import org.apache.camel.spi.Language;
import org.apache.camel.spi.PropertiesComponent;
+import org.apache.camel.spi.UuidGenerator;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.ClassicUuidGenerator;
+import org.apache.camel.support.DefaultUuidGenerator;
import org.apache.camel.support.ExchangeHelper;
import org.apache.camel.support.GroupIterator;
import org.apache.camel.support.LanguageHelper;
import org.apache.camel.support.MessageHelper;
+import org.apache.camel.support.ShortUuidGenerator;
+import org.apache.camel.support.SimpleUuidGenerator;
import org.apache.camel.util.FileUtil;
import org.apache.camel.util.InetAddressUtil;
import org.apache.camel.util.ObjectHelper;
@@ -69,6 +77,14 @@ public final class CSimpleHelper {
private CSimpleHelper() {
}
+ public static <T> T convertTo(Exchange exchange, Class<T> type, Object
value) {
+ return exchange.getContext().getTypeConverter().convertTo(type,
exchange, value);
+ }
+
+ public static <T> T tryConvertTo(Exchange exchange, Class<T> type, Object
value) {
+ return exchange.getContext().getTypeConverter().tryConvertTo(type,
exchange, value);
+ }
+
public static <T> T messageAs(Exchange exchange, Class<T> type) {
return exchange.getMessage(type);
}
@@ -355,9 +371,7 @@ public final class CSimpleHelper {
private static Object doDate(Exchange exchange, String commandWithOffsets,
String timezone, String pattern) {
final String command = commandWithOffsets.split("[+-]", 2)[0].trim();
final List<Long> offsets =
LanguageHelper.captureOffsets(commandWithOffsets, OFFSET_PATTERN);
-
Date date = evalDate(exchange, command);
-
return LanguageHelper.applyDateOffsets(date, offsets, pattern,
timezone);
}
@@ -695,4 +709,53 @@ public final class CSimpleHelper {
return num;
}
+ public static Object join(Exchange exchange, Object value, String
separator, String prefix) {
+ Iterator<?> it = convertTo(exchange, Iterator.class, value);
+ StringBuilder sb = new StringBuilder();
+ while (it.hasNext()) {
+ Object o = it.next();
+ if (o != null) {
+ String s = tryConvertTo(exchange, String.class, o);
+ if (s != null) {
+ if (!sb.isEmpty()) {
+ sb.append(separator);
+ }
+ if (prefix != null) {
+ sb.append(prefix);
+ }
+ sb.append(s);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ public static Object hash(Exchange exchange, Object value, String
algorithm) {
+ byte[] data = convertTo(exchange, byte[].class, value);
+ if (data != null && data.length > 0) {
+ try {
+ MessageDigest digest = MessageDigest.getInstance(algorithm);
+ byte[] bytes = digest.digest(data);
+ return StringHelper.bytesToHex(bytes);
+ } catch (Exception e) {
+ throw
CamelExecutionException.wrapCamelExecutionException(exchange, e);
+ }
+ }
+ return null;
+ }
+
+ public static UuidGenerator createUuidGenerator(Exchange exchange, String
generator) {
+ if ("classic".equalsIgnoreCase(generator)) {
+ return new ClassicUuidGenerator();
+ } else if ("short".equals(generator)) {
+ return new ShortUuidGenerator();
+ } else if ("simple".equals(generator)) {
+ return new SimpleUuidGenerator();
+ } else if (generator == null || "default".equals(generator)) {
+ return new DefaultUuidGenerator();
+ } else {
+ // lookup custom generator
+ return CamelContextHelper.mandatoryLookup(exchange.getContext(),
generator, UuidGenerator.class);
+ }
+ }
}
diff --git
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
index 0b9d67349bb..01f2080a7c2 100644
---
a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
+++
b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/BaseSimpleParser.java
@@ -41,6 +41,16 @@ import org.apache.camel.language.simple.types.TokenType;
*/
public abstract class BaseSimpleParser {
+ /**
+ * Marker used for start of generated code for csimple
+ */
+ public static final String CODE_START = "\"@@code[[";
+
+ /**
+ * Marker used for end of generated code for csimple
+ */
+ public static final String CODE_END = "]]code@@\"";
+
protected final CamelContext camelContext;
protected final String expression;
protected final List<SimpleToken> tokens = new ArrayList<>();
@@ -106,7 +116,7 @@ public abstract class BaseSimpleParser {
}
/**
- * Prepares blocks, such as functions, single or double quoted texts.
+ * Prepares blocks, such as functions, single or double-quoted texts.
* <p/>
* This process prepares the {@link Block}s in the AST. This is done by
linking child {@link SimpleNode nodes} which
* are within the start and end of the blocks, as child to the given
block. This is done to have the AST graph
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 54e59bba477..dab6fdb1ec4 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
@@ -32,6 +32,7 @@ import org.apache.camel.Exchange;
import org.apache.camel.ExchangePropertyKey;
import org.apache.camel.Expression;
import org.apache.camel.InvalidPayloadException;
+import org.apache.camel.Predicate;
import org.apache.camel.spi.ClassResolver;
import org.apache.camel.spi.ExchangeFormatter;
import org.apache.camel.spi.Language;
@@ -176,6 +177,41 @@ public final class SimpleExpressionBuilder {
};
}
+ /**
+ * A ternary condition expression
+ */
+ public static Expression iifExpression(final String predicate, final
String trueValue, final String falseValue) {
+ return new ExpressionAdapter() {
+ private Predicate pred;
+ private Expression expTrue;
+ private Expression expFalse;
+
+ @Override
+ public void init(CamelContext context) {
+ pred =
context.resolveLanguage("simple").createPredicate(predicate);
+ pred.init(context);
+ expTrue =
context.resolveLanguage("simple").createExpression(trueValue);
+ expTrue.init(context);
+ expFalse =
context.resolveLanguage("simple").createExpression(falseValue);
+ expFalse.init(context);
+ }
+
+ @Override
+ public Object evaluate(Exchange exchange) {
+ if (pred.matches(exchange)) {
+ return expTrue.evaluate(exchange, Object.class);
+ } else {
+ return expFalse.evaluate(exchange, Object.class);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "iif(" + predicate + "," + trueValue + "," + falseValue
+ ")";
+ }
+ };
+ }
+
/**
* Joins together the values from the expression
*/
@@ -391,7 +427,7 @@ public final class SimpleExpressionBuilder {
}
/**
- * Returns a uuid string based on the given generator (default, classic,
short, simple)
+ * Returns an uuid string based on the given generator (default, classic,
short, simple)
*/
public static Expression uuidExpression(final String generator) {
return new ExpressionAdapter() {
@@ -1110,7 +1146,7 @@ public final class SimpleExpressionBuilder {
}
/**
- * Returns the expression for the exchanges exception invoking methods
defined in a simple OGNL notation
+ * Returns the expression for the exchange's exception invoking methods
defined in a simple OGNL notation
*
* @param ognl methods to invoke on the body in a simple OGNL syntax
*/
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 03587e0d8ee..63578bfccbc 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
@@ -95,9 +95,9 @@ public class SimpleExpressionParser extends BaseSimpleParser {
nextToken();
}
- // now after parsing we need a bit of work to do, to make it easier to
turn the tokens
+ // now after parsing, we need a bit of work to do, to make it easier
to turn the tokens
// into an ast, and then from the ast, to Camel expression(s).
- // hence why there is a number of tasks going on below to accomplish
this
+ // hence why there are a number of tasks going on below to accomplish
this
// turn the tokens into the ast model
parseAndCreateAstModel();
@@ -153,7 +153,7 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
continue;
}
- // if no token was created then its a character/whitespace/escaped
symbol
+ // if no token was created, then it's a
character/whitespace/escaped symbol
// which we need to add together in the same image
if (imageToken == null) {
imageToken = new LiteralExpression(token);
@@ -240,7 +240,10 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
}
}
}
- return sb.toString();
+ String code = sb.toString();
+ code = code.replace(BaseSimpleParser.CODE_START, "");
+ code = code.replace(BaseSimpleParser.CODE_END, "");
+ return code;
}
// --------------------------------------------------------------
@@ -249,11 +252,11 @@ public class SimpleExpressionParser extends
BaseSimpleParser {
// the expression parser only understands
// - template = literal texts with can contain embedded functions
- // - function = simple functions such as ${body} etc
- // - unary operator = operator attached to the left hand side node
+ // - function = simple functions such as ${body} etc.
+ // - unary operator = operator attached to the left-hand side node
protected void templateText() {
- // for template we accept anything but functions
+ // for template, we accept anything but functions
while (!token.getType().isFunctionStart() &&
!token.getType().isFunctionEnd() && !token.getType().isEol()) {
nextToken();
}
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 b7df246923b..1ba3f142fca 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
@@ -187,7 +187,10 @@ public class SimplePredicateParser extends
BaseSimpleParser {
sb.append(exp);
}
}
- return sb.toString();
+ String code = sb.toString();
+ code = code.replace(BaseSimpleParser.CODE_START, "");
+ code = code.replace(BaseSimpleParser.CODE_END, "");
+ return code;
}
/**
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 76931d3b3c8..e6349c533d7 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
@@ -26,6 +26,7 @@ import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
+import org.apache.camel.language.simple.BaseSimpleParser;
import org.apache.camel.language.simple.types.BinaryOperatorType;
import org.apache.camel.language.simple.types.SimpleIllegalSyntaxException;
import org.apache.camel.language.simple.types.SimpleParserException;
@@ -282,6 +283,10 @@ public class BinaryExpression extends BaseSimpleNode {
@Override
public String createCode(String expression) throws SimpleParserException {
+ return BaseSimpleParser.CODE_START + doCreateCode(expression) +
BaseSimpleParser.CODE_END;
+ }
+
+ private String doCreateCode(String expression) throws
SimpleParserException {
org.apache.camel.util.ObjectHelper.notNull(left, "left node", this);
org.apache.camel.util.ObjectHelper.notNull(right, "right node", this);
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 99caa50af08..9fc06ecd133 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
@@ -20,6 +20,7 @@ import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
+import org.apache.camel.language.simple.BaseSimpleParser;
import org.apache.camel.language.simple.types.LogicalOperatorType;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
@@ -123,6 +124,10 @@ public class LogicalExpression extends BaseSimpleNode {
@Override
public String createCode(String expression) throws SimpleParserException {
+ return BaseSimpleParser.CODE_START + doCreateCode(expression) +
BaseSimpleParser.CODE_END;
+ }
+
+ private String doCreateCode(String expression) throws
SimpleParserException {
ObjectHelper.notNull(left, "left node", this);
ObjectHelper.notNull(right, "right node", this);
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 4705ae54880..e0801692538 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
@@ -24,6 +24,7 @@ import java.util.Map;
import org.apache.camel.CamelContext;
import org.apache.camel.Expression;
import org.apache.camel.RuntimeCamelException;
+import org.apache.camel.language.simple.BaseSimpleParser;
import org.apache.camel.language.simple.SimpleExpressionBuilder;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
@@ -278,7 +279,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
if (remainder != null) {
String[] parts = remainder.split(":", 2);
if (parts.length > 2) {
- throw new SimpleParserException("Valid syntax:
${propertiesExist:key was: " + function, token.getIndex());
+ throw new SimpleParserException("Valid syntax:
${propertiesExist:key} was: " + function, token.getIndex());
}
String key = parts[0];
boolean negate = key != null && key.startsWith("!");
@@ -314,7 +315,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
if (remainder != null) {
Expression exp = SimpleExpressionBuilder.typeExpression(remainder);
exp.init(camelContext);
- // we want to cache this expression so we wont re-evaluate it as
the type/constant wont change
+ // we want to cache this expression, so we won't re-evaluate it as
the type/constant won't change
return SimpleExpressionBuilder.cacheExpression(exp);
}
@@ -403,7 +404,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
remainder = ifStartsWithReturnRemainder("in.body", function);
}
if (remainder != null) {
- // OGNL must start with a . ? or [
+ // OGNL must start with a ".", "?" or "[".
boolean ognlStart = remainder.startsWith(".") ||
remainder.startsWith("?") || remainder.startsWith("[");
boolean invalid = !ognlStart ||
OgnlHelper.isInvalidValidOgnlExpression(remainder);
if (invalid) {
@@ -702,7 +703,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',',
false);
if (tokens.length > 3) {
throw new SimpleParserException(
- "Valid syntax: ${replace(num,num,expression)} was: " +
function, token.getIndex());
+ "Valid syntax: ${substring(num,num,expression)} was: "
+ function, token.getIndex());
}
String num1 = tokens[0];
String num2 = "0";
@@ -841,6 +842,21 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
return SimpleExpressionBuilder.newEmptyExpression(value);
}
+ // iif function
+ remainder = ifStartsWithReturnRemainder("iif(", function);
+ if (remainder != null) {
+ String values = StringHelper.beforeLast(remainder, ")");
+ if (values == null || ObjectHelper.isEmpty(values)) {
+ throw new SimpleParserException(
+ "Valid syntax:
${iif(predicate,trueExpression,falseExpression)} was: " + function,
token.getIndex());
+ }
+ String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',');
+ if (tokens.length > 3) {
+ throw new SimpleParserException(
+ "Valid syntax:
${iif(predicate,trueExpression,falseExpression)} was: " + function,
token.getIndex());
+ }
+ return SimpleExpressionBuilder.iifExpression(tokens[0].trim(),
tokens[1].trim(), tokens[2].trim());
+ }
return null;
}
@@ -857,6 +873,10 @@ public class SimpleFunctionExpression extends
LiteralExpression {
@Override
public String createCode(String expression) throws SimpleParserException {
+ return BaseSimpleParser.CODE_START + doCreateCode(expression) +
BaseSimpleParser.CODE_END;
+ }
+
+ private String doCreateCode(String expression) throws
SimpleParserException {
String function = getText();
// return the function directly if we can create function without
analyzing the prefix
@@ -907,6 +927,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
return "exceptionAs(exchange, " + type + ")" +
ognlCodeMethods(remainder, type);
}
+
// Exception OGNL
remainder = ifStartsWithReturnRemainder("exception", function);
if (remainder != null) {
@@ -1372,7 +1393,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
// the key can contain index as it may be a map header.foo[0]
- // and the key can also be OGNL (eg if there is a dot)
+ // and the key can also be OGNL (e.g., if there is a dot)
boolean index = false;
List<String> parts = splitOgnl(key);
if (!parts.isEmpty()) {
@@ -1389,7 +1410,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
if (index) {
// is there any index, then we should use headerAsIndex
function instead
- // (use splitOgnl which assembles multiple indexes into a
single part)
+ // (use splitOgnl which assembles multiple indexes into a
single part?)
String func = "headerAsIndex(\"" + parts.get(0) + "\",
Object.class, \"" + parts.get(1) + "\")";
if (parts.size() > 2) {
String last = String.join("", parts.subList(2,
parts.size()));
@@ -1675,10 +1696,10 @@ public class SimpleFunctionExpression extends
LiteralExpression {
+ function,
token.getIndex());
}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',');
+ String[] tokens = codeSplitSafe(values, ',', true, true);
if (tokens.length > 2) {
throw new SimpleParserException(
- "Valid syntax: ${replace(num,num)} was: " + function,
token.getIndex());
+ "Valid syntax: ${substring(num,num)} was: " +
function, token.getIndex());
}
String num1 = tokens[0];
String num2 = "0";
@@ -1721,7 +1742,7 @@ public class SimpleFunctionExpression extends
LiteralExpression {
"Valid syntax: ${replace(from,to)} was: " + function,
token.getIndex());
}
- String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',');
+ String[] tokens = codeSplitSafe(values, ',', true, false);
if (tokens.length > 2) {
throw new SimpleParserException(
"Valid syntax: ${replace(from,to)} was: " + function,
token.getIndex());
@@ -1778,10 +1799,49 @@ public class SimpleFunctionExpression extends
LiteralExpression {
return "messageHistory(exchange, true)";
}
+ // join
+ remainder = ifStartsWithReturnRemainder("join(", function);
+ if (remainder != null) {
+ String values = StringHelper.beforeLast(remainder, ")");
+ String separator = "\",\"";
+ String prefix = null;
+ String exp = "body";
+ if (ObjectHelper.isNotEmpty(values)) {
+ String[] tokens = codeSplitSafe(values, ',', true, true);
+ if (tokens.length > 3) {
+ throw new SimpleParserException(
+ "Valid syntax:
${join(separator,prefix,expression)} was: " + function, token.getIndex());
+ }
+ // single quotes should be double quotes
+ for (int i = 0; i < tokens.length; i++) {
+ String s = tokens[i];
+ if (StringHelper.isSingleQuoted(s)) {
+ s = StringHelper.removeLeadingAndEndingQuotes(s);
+ s = StringQuoteHelper.doubleQuote(s);
+ tokens[i] = s;
+ } else if (i < 2 && !StringHelper.isDoubleQuoted(s)) {
+ s = StringQuoteHelper.doubleQuote(s);
+ tokens[i] = s;
+ }
+ }
+ if (tokens.length == 3) {
+ separator = tokens[0];
+ prefix = tokens[1];
+ exp = tokens[2];
+ } else if (tokens.length == 2) {
+ separator = tokens[0];
+ prefix = tokens[1];
+ } else {
+ separator = tokens[0];
+ }
+ }
+ return "var val = " + exp + ";\n return join(exchange, val,
" + separator + ", " + prefix + ");";
+ }
+
// empty function
remainder = ifStartsWithReturnRemainder("empty(", function);
if (remainder != null) {
- String value = StringHelper.before(remainder, ")");
+ String value = StringHelper.beforeLast(remainder, ")");
if (ObjectHelper.isEmpty(value)) {
throw new SimpleParserException(
"Valid syntax: ${empty(<type>)} but was: " + function,
token.getIndex());
@@ -1790,6 +1850,80 @@ public class SimpleFunctionExpression extends
LiteralExpression {
return "empty(exchange, " + value + ")";
}
+ // hash function
+ remainder = ifStartsWithReturnRemainder("hash(", function);
+ if (remainder != null) {
+ String values = StringHelper.beforeLast(remainder, ")");
+ if (values == null || ObjectHelper.isEmpty(values)) {
+ throw new SimpleParserException(
+ "Valid syntax: ${hash(value,algorithm)} or
${hash(value)} was: " + function, token.getIndex());
+ }
+ String[] tokens = codeSplitSafe(values, ',', true, true);
+ if (tokens.length > 2) {
+ throw new SimpleParserException(
+ "Valid syntax: ${hash(value,algorithm)} or
${hash(value)} was: " + function, token.getIndex());
+ }
+ // single quotes should be double quotes
+ for (int i = 0; i < tokens.length; i++) {
+ String s = tokens[i];
+ if (StringHelper.isSingleQuoted(s)) {
+ s = StringHelper.removeLeadingAndEndingQuotes(s);
+ s = StringQuoteHelper.doubleQuote(s);
+ tokens[i] = s;
+ }
+ }
+ String algo = "\"SHA-256\"";
+ if (tokens.length == 2) {
+ algo = tokens[1];
+ if (!StringHelper.isQuoted(algo)) {
+ algo = StringQuoteHelper.doubleQuote(algo);
+ }
+ }
+ return "var val = " + tokens[0] + ";\n return
hash(exchange, val, " + algo + ");";
+ }
+
+ // uuid function
+ remainder = ifStartsWithReturnRemainder("uuid", function);
+ if (remainder == null && "uuid".equals(function)) {
+ remainder = "(default)";
+ }
+ if (remainder != null) {
+ String generator = StringHelper.between(remainder, "(", ")");
+ if (generator == null) {
+ generator = "default";
+ }
+ generator = StringQuoteHelper.doubleQuote(generator);
+ return "if (uuid == null) uuid = createUuidGenerator(exchange, " +
generator + "); return uuid.generateUuid();";
+ }
+
+ // iif function
+ remainder = ifStartsWithReturnRemainder("iif(", function);
+ if (remainder != null) {
+ String values = StringHelper.beforeLast(remainder, ")");
+ if (values == null || ObjectHelper.isEmpty(values)) {
+ throw new SimpleParserException(
+ "Valid syntax:
${iif(predicate,trueExpression,falseExpression)} was: " + function,
token.getIndex());
+ }
+ String[] tokens = codeSplitSafe(values, ',', true, true);
+ if (tokens.length != 3) {
+ throw new SimpleParserException(
+ "Valid syntax:
${iif(predicate,trueExpression,falseExpression)} was: " + function,
token.getIndex());
+ }
+ // single quotes should be double quotes
+ for (int i = 0; i < 3; i++) {
+ String s = tokens[i];
+ if (StringHelper.isSingleQuoted(s)) {
+ s = StringHelper.removeLeadingAndEndingQuotes(s);
+ s = StringQuoteHelper.doubleQuote(s);
+ tokens[i] = s;
+ }
+ }
+
+ return "Object o = " + tokens[0]
+ + ";\n boolean b = convertTo(exchange,
boolean.class, o);\n return b ? "
+ + tokens[1] + " : " + tokens[2];
+ }
+
return null;
}
@@ -1898,4 +2032,149 @@ public class SimpleFunctionExpression extends
LiteralExpression {
}
}
+ private static String[] codeSplitSafe(String input, char separator,
boolean trim, boolean keepQuotes) {
+ if (input == null) {
+ return null;
+ }
+
+ if (input.indexOf(separator) == -1) {
+ if (input.length() > 1) {
+ char ch = input.charAt(0);
+ char ch2 = input.charAt(input.length() - 1);
+ boolean singleQuoted = ch == '\'' && ch2 == '\'';
+ boolean doubleQuoted = ch == '"' && ch2 == '"';
+ if (!keepQuotes && (singleQuoted || doubleQuoted)) {
+ input = input.substring(1, input.length() - 1);
+ // do not trim quoted text
+ } else if (trim) {
+ input = input.trim();
+ }
+ }
+ // no separator in data, so return single string with input as is
+ return new String[] { input };
+ }
+
+ List<String> answer = new ArrayList<>();
+ StringBuilder sb = new StringBuilder();
+
+ int codeLevel = 0;
+ boolean singleQuoted = false;
+ boolean doubleQuoted = false;
+ boolean separating = false;
+
+ for (int i = 0; i < input.length(); i++) {
+ char ch = input.charAt(i);
+ char prev = i > 0 ? input.charAt(i - 1) : 0;
+ boolean isQuoting = singleQuoted || doubleQuoted;
+ boolean last = i == input.length() - 1;
+
+ // do not split inside code blocks
+ if (input.indexOf(BaseSimpleParser.CODE_START, i) == i) {
+ codeLevel++;
+ sb.append(BaseSimpleParser.CODE_START);
+ i = i + BaseSimpleParser.CODE_START.length() - 1;
+ continue;
+ } else if (input.indexOf(BaseSimpleParser.CODE_END, i) == i) {
+ codeLevel--;
+ sb.append(BaseSimpleParser.CODE_END);
+ i = i + BaseSimpleParser.CODE_END.length() - 1;
+ continue;
+ }
+ if (codeLevel > 0) {
+ sb.append(ch);
+ continue;
+ }
+
+ if (!doubleQuoted && ch == '\'') {
+ if (singleQuoted && prev == ch && sb.isEmpty()) {
+ // its an empty quote so add empty text
+ if (keepQuotes) {
+ answer.add("''");
+ } else {
+ answer.add("");
+ }
+ }
+ // special logic needed if this quote is the end
+ if (last) {
+ if (singleQuoted && !sb.isEmpty()) {
+ String text = sb.toString();
+ // do not trim a quoted string
+ if (keepQuotes) {
+ answer.add(text + "'"); // append ending quote
+ } else {
+ answer.add(text);
+ }
+ sb.setLength(0);
+ }
+ break; // break out as we are finished
+ }
+ singleQuoted = !singleQuoted;
+ if (keepQuotes) {
+ sb.append(ch);
+ }
+ continue;
+ } else if (!singleQuoted && ch == '"') {
+ if (doubleQuoted && prev == ch && sb.isEmpty()) {
+ // its an empty quote so add empty text
+ if (keepQuotes) {
+ answer.add("\""); // append ending quote
+ } else {
+ answer.add("");
+ }
+ }
+ // special logic needed if this quote is the end
+ if (last) {
+ if (doubleQuoted && !sb.isEmpty()) {
+ String text = sb.toString();
+ // do not trim a quoted string
+ if (keepQuotes) {
+ answer.add(text + "\"");
+ } else {
+ answer.add(text);
+ }
+ sb.setLength(0);
+ }
+ break; // break out as we are finished
+ }
+ doubleQuoted = !doubleQuoted;
+ if (keepQuotes) {
+ sb.append(ch);
+ }
+ continue;
+ } else if (!isQuoting && ch == separator) {
+ separating = true;
+ // add as answer if we are not in a quote
+ if (!sb.isEmpty()) {
+ String text = sb.toString();
+ if (trim) {
+ text = text.trim();
+ }
+ answer.add(text);
+ sb.setLength(0);
+ }
+ // we should avoid adding the separator
+ continue;
+ }
+
+ if (trim && !isQuoting && separating && separator != ' ' && ch ==
' ') {
+ continue;
+ }
+ separating = false;
+
+ // append char
+ sb.append(ch);
+ }
+
+ // any leftover
+ if (!sb.isEmpty()) {
+ String text = sb.toString();
+ if (trim) {
+ text = text.trim();
+ }
+ answer.add(text);
+ }
+
+ return answer.toArray(new String[0]);
+ }
+
}
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 ddd8f9e3f23..d62a9cd7fc0 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
@@ -151,7 +151,7 @@ public class SimpleFunctionStart extends BaseSimpleNode
implements BlockStart {
@Override
public String createCode(String expression) throws SimpleParserException {
String answer;
- // a function can either be a simple literal function, or contain
nested functions
+ // 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);
} else {
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 e87c8a5d4e4..e39ab05d16c 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
@@ -22,6 +22,7 @@ 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.BaseSimpleParser;
import org.apache.camel.language.simple.types.SimpleParserException;
import org.apache.camel.language.simple.types.SimpleToken;
import org.apache.camel.language.simple.types.UnaryOperatorType;
@@ -147,6 +148,10 @@ public class UnaryExpression extends BaseSimpleNode {
@Override
public String createCode(String expression) throws SimpleParserException {
+ return BaseSimpleParser.CODE_START + doCreateCode(expression) +
BaseSimpleParser.CODE_END;
+ }
+
+ private String doCreateCode(String expression) throws
SimpleParserException {
ObjectHelper.notNull(left, "left node", this);
final String number = left.createCode(expression);
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 46bec4cc7b7..3b16729a7b6 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
@@ -2114,6 +2114,21 @@ public class SimpleTest extends LanguageTestSupport {
assertExpression("${substring(0,0,${header.foo})}", "1234567890");
}
+ @Test
+ public void testIif() {
+ exchange.getIn().setHeader("foo", 44);
+ assertExpression("${iif(${header.foo} > 0,'positive','negative')}",
"positive");
+ exchange.getIn().setHeader("foo", -123);
+ assertExpression("${iif(${header.foo} > 0,'positive','negative')}",
"negative");
+
+ exchange.getIn().setBody("Hello World");
+ exchange.getIn().setHeader("foo", 44);
+ assertExpression("${iif(${header.foo} > 0,${body},'Bye World')}",
"Hello World");
+ exchange.getIn().setHeader("foo", -123);
+ assertExpression("${iif(${header.foo} > 0,${body},'Bye World')}", "Bye
World");
+ assertExpression("${iif(${header.foo} > 0,${body},${null})}", null);
+ }
+
@Test
public void testListRemoveByInstance() {
List<Object> data = new ArrayList<>();