This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch ib in repository https://gitbox.apache.org/repos/asf/camel.git
commit 91bd1aff041a1f22032352aa589fe662786785c1 Author: Claus Ibsen <[email protected]> AuthorDate: Wed Jan 21 17:07:45 2026 +0100 CAMEL-22882: simple language - Add support for init block --- .../modules/languages/pages/simple-language.adoc | 72 +++++++++- .../camel/language/simple/BaseSimpleParser.java | 8 +- .../language/simple/SimpleExpressionParser.java | 42 +++++- .../language/simple/SimpleInitBlockParser.java | 154 +++++++++++++++++++++ .../language/simple/SimplePredicateParser.java | 20 ++- .../camel/language/simple/SimpleTokenizer.java | 41 +++++- .../camel/language/simple/ast/InitExpression.java | 145 +++++++++++++++++++ .../{TokenType.java => InitOperatorType.java} | 40 +++--- .../language/simple/types/SimpleTokenType.java | 14 ++ .../camel/language/simple/types/TokenType.java | 2 + .../camel/language/simple/SimpleInitBlockTest.java | 77 +++++++++++ .../camel/support/builder/ExpressionBuilder.java | 25 ++++ .../camel/support/builder/PredicateBuilder.java | 11 ++ 13 files changed, 620 insertions(+), 31 deletions(-) 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 f0d9f7973d8d..e42c575a520c 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 @@ -122,8 +122,6 @@ The Simple language has many built-in functions which allows access to various p NOTE: Some functions take 1 or more arguments enclosed in parentheses, and arguments separated by comma (ie `${replace('custID','customerId')}`. -TIP: - === Camel Functions [width="100%",cols="10%,10%,80%",options="header",] @@ -192,6 +190,7 @@ TIP: [width="100%",cols="10%,10%,80%",options="header",] |==== +|Function |Response Type |Description |`collate(size)` | `List` | The collate function iterates the message body and groups the data into sub lists of specified size. This can be used with the Splitter EIP to split a message body and group/batch the split sub message into a group of N sub lists. This method works similar to the collate method in Groovy. |`distinct(val1,val2,...)` | `Set` | Returns a set of all the values with duplicates removed |`empty(kind)` | `<T>` | *Deprecated* Use `createEmpty` instead. @@ -207,6 +206,7 @@ TIP: [width="100%",cols="10%,10%,80%",options="header",] |==== +|Function |Response Type |Description |`iif(predicate,trueExp,falseExp`) | `Object` | The inlined if function 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. |`isEmpty` | `boolean` | Whether the message body is `null` or empty (list/map types are tested if they have 0 elements). |`isEmpty(exp)` | `boolean` | Whether the expression is `null` or empty (list/map types are tested if they have 0 elements). @@ -255,6 +255,7 @@ TIP: [width="100%",cols="10%,10%,80%",options="header",] |==== +|Function |Response Type |Description |`env.key` | `String` | Refers to the OS system environment variable with the given key. For example `env.HOME` to refer to the home directory. |`hash(exp,algorithm)` | `String` | Returns a hashed value (string in hex decimal) of the given expression. The algorithm can be `SHA-256` (default) or `SHA3-256`. |`hostname` | `String` | Returns the local hostname (may be `null` if not possible to resolve). @@ -268,8 +269,9 @@ TIP: === String Functions -0[width="100%",cols="10%,10%,80%",options="header",] +[width="100%",cols="10%,10%,80%",options="header",] |==== +|Function |Response Type |Description |`capitalize()` | `String` | Capitalizes the message body as a String value (upper case every words) |`capitalize(exp)` | `String` | Capitalizes the expression as a String value (upper case every words) |`concat(exp,exp,separator)` | `String` | Performs a string concat using two expressions (message body as default) with optional separator (uses comma by default). @@ -300,6 +302,7 @@ TIP: [width="100%",cols="10%,10%,80%",options="header",] |==== +|Function |Response Type |Description |`jq(exp)` | `Object` | When working with JSon data, then this allows using the JQ language, for example, to extract data from the message body (in JSon format). This requires having camel-jq JAR on the classpath. |`jq(input,exp)` | `Object` | Same as `jq(exp)` but to use the _input_ expression as the source of the JSon document. |`jsonpath(exp)` | `Object` | When working with JSon data, then this allows using the JsonPath language, for example, to extract data from the message body (in JSon format). This requires having camel-jsonpath JAR on the classpath. @@ -617,6 +620,69 @@ And of course the `||` is also supported. The sample would be: simple("${header.title} contains 'Camel' || ${header.type'} == 'gold'") ----- +== Init Blocks + +Starting from *Camel 4.18* you can in the top of your Simple expressions declare an _initialization_ block that are used +to define a set of local variables that are pre-computed, and can be used in the following Simple expression. This allows +to reuse variables, and also avoid making the simple expression complicated when having inlined functions, and in general +making using the simple expression easier to maintain and understand. + +The _init block_ is declared as follows: + +[source,text] +---- +$init{ + // init block + // goes here + // ... +}init$ +// here is the regular simple expression +---- + +Notice how the block uses the `$init{` ... `}init$` markers to indicate the start and end of the block. + +Inside the init block, then you can assign local variables in the syntax `$$key := ...` where you can then use simple language to declare +the value of the variable. + +In the example below the `foo` variable is assigned a constant value of `Hello foo`, while `bar` variable is a string value with the dynamic value +of the message body. + +[source,text] +---- +$init{ + $$foo := 'Hello foo' + $$bar := 'Hi from ${body}' +}init$ +---- + +Yes you can use the full power of the simple language and all its functions, for example: + +[source,text] +---- +$init{ + $$foo := ${upper('Hello $body}'} + $$bar := ${iif(${header.code} > 999,'Gold','Silver')} +}init$ +---- + +The local variables can then easily be used in the Simple language either using the `${variable.foo}` syntax +to the same shorthand syntax `$$foo`. + +For example + +[source,text] +---- +$init{ + $$greeting := ${upper('Hello $body}'} + $$level := ${iif(${header.code} > 999,'Gold','Silver')} +}init$ +{ + "message": "$$greeting", + "status": "$$level" +} +---- + + == OGNL Expression Support The xref:simple-language.adoc[Simple] and xref:simple-language.adoc[Bean] languages support a _OGNL like_ notation for invoking methods (using reflection) in a fluent builder like style. 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 359f7a979ccb..52d4c98907ae 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 @@ -54,9 +54,12 @@ public abstract class BaseSimpleParser { public static final String CODE_END = "]]code@@\""; protected final CamelContext camelContext; - protected final String expression; + protected final String originalExpression; + // regular simple expression protected final List<SimpleToken> tokens = new ArrayList<>(); protected final List<SimpleNode> nodes = new ArrayList<>(); + // tokenizer state + protected String expression; protected SimpleToken token; protected int previousIndex; protected int index; @@ -64,8 +67,9 @@ public abstract class BaseSimpleParser { protected BaseSimpleParser(CamelContext camelContext, String expression, boolean allowEscape) { this.camelContext = camelContext; - this.expression = expression; + this.originalExpression = expression; this.allowEscape = allowEscape; + this.expression = expression; } /** 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 7ef8b8271dc7..4e60bc85b640 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 @@ -23,6 +23,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.camel.CamelContext; import org.apache.camel.Expression; +import org.apache.camel.language.simple.ast.InitExpression; import org.apache.camel.language.simple.ast.LiteralExpression; import org.apache.camel.language.simple.ast.LiteralNode; import org.apache.camel.language.simple.ast.OtherExpression; @@ -64,8 +65,26 @@ public class SimpleExpressionParser extends BaseSimpleParser { public Expression parseExpression() { try { + // parse init block + SimpleInitBlockParser initParser + = new SimpleInitBlockParser(camelContext, expression, allowEscape, cacheExpression); + Expression init = initParser.parseExpression(); + if (init != null && SimpleTokenizer.hasInitBlock(originalExpression)) { + this.expression = StringHelper.after(originalExpression, SimpleTokenizer.INIT_END); + // use $$key as local variable + for (String key : initParser.getInitKeys()) { + this.expression = this.expression.replace("$$" + key, "${variable." + key + "}"); + } + } + + // parse simple expression parseTokens(); - return doParseExpression(); + Expression exp = doParseExpression(); + // include init block in expression + if (init != null) { + exp = ExpressionBuilder.concatExpression(List.of(init, exp)); + } + return exp; } catch (SimpleParserException e) { // catch parser exception and turn that into a syntax exceptions throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e); @@ -142,20 +161,35 @@ public class SimpleExpressionParser extends BaseSimpleParser { } } + /** + * Second step parsing into an expression + */ + protected Expression doParseInitExpression() { + // create and return as a Camel expression + List<Expression> expressions = createExpressions(); + if (expressions.isEmpty()) { + return null; + } else if (expressions.size() == 1) { + return expressions.get(0); + } else { + return ExpressionBuilder.eval(expressions); + } + } + /** * Removes any ignorable whitespace tokens before and after other operators. * <p/> * During the initial parsing (input -> tokens), then there may be excessive whitespace tokens, which can safely be * removed, which makes the succeeding parsing easier. */ - private void removeIgnorableWhiteSpaceTokens() { + protected void removeIgnorableWhiteSpaceTokens() { // white space should be removed before and after the other operator List<SimpleToken> toRemove = new ArrayList<>(); for (int i = 1; i < tokens.size() - 1; i++) { SimpleToken prev = tokens.get(i - 1); SimpleToken cur = tokens.get(i); SimpleToken next = tokens.get(i + 1); - if (cur.getType().isOther()) { + if (cur.getType().isOther() || cur.getType().isInit()) { if (prev.getType().isWhitespace()) { toRemove.add(prev); } @@ -228,6 +262,8 @@ public class SimpleExpressionParser extends BaseSimpleParser { } } else if (token.getType().isOther()) { return new OtherExpression(token); + } else if (token.getType().isInit()) { + return new InitExpression(token); } // by returning null, we will let the parser determine what to do diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java new file mode 100644 index 000000000000..7e1a4c3ca55c --- /dev/null +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleInitBlockParser.java @@ -0,0 +1,154 @@ +/* + * 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.language.simple; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.camel.CamelContext; +import org.apache.camel.Expression; +import org.apache.camel.language.simple.ast.InitExpression; +import org.apache.camel.language.simple.ast.LiteralNode; +import org.apache.camel.language.simple.ast.SimpleNode; +import org.apache.camel.language.simple.types.TokenType; +import org.apache.camel.util.StringHelper; + +class SimpleInitBlockParser extends SimpleExpressionParser { + + private final Set<String> initKeys = new LinkedHashSet<>(); + + public SimpleInitBlockParser(CamelContext camelContext, String expression, boolean allowEscape, + Map<String, Expression> cacheExpression) { + super(camelContext, expression, allowEscape, cacheExpression); + } + + public Set<String> getInitKeys() { + return initKeys; + } + + @Override + public Expression parseExpression() { + // parse init block + try { + SimpleTokenizer.INIT_MODE.set(true); + parseInitTokens(); + return doParseInitExpression(); + } finally { + SimpleTokenizer.INIT_MODE.set(false); + } + } + + protected List<SimpleNode> parseInitTokens() { + // prepare the expression to use for parsing + if (SimpleTokenizer.hasInitBlock(originalExpression)) { + this.expression = StringHelper.between(originalExpression, SimpleTokenizer.INIT_START, SimpleTokenizer.INIT_END); + } else { + this.expression = ""; + } + + clear(); + initKeys.clear(); + + // run in init-mode + SimpleTokenizer.INIT_MODE.set(true); + + // parse the expression using the following grammar + nextToken(); + while (!token.getType().isEol()) { + // an expression supports just template (eg text), functions, unary, or other operator + initText(); + templateText(); + functionText(); + unaryOperator(); + otherOperator(); + nextToken(); + } + + // 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 are a number of tasks going on below to accomplish this + + // remove any ignorable white space tokens + removeIgnorableWhiteSpaceTokens(); + // prepare for any local variables to use $$ syntax in simple expression + + // turn the tokens into the ast model + parseAndCreateAstModel(); + // compact and stack blocks (eg function start/end) + prepareBlocks(); + // compact and stack init blocks + prepareInitBlocks(); + // compact and stack unary operators + prepareUnaryExpressions(); + // compact and stack other expressions + prepareOtherExpressions(); + + return nodes; + } + + // special for init blocks that are only available in the top + // $$name := <function> + // $$name2 := <function> + protected boolean initText() { + while (!token.getType().isInitVariable() && !token.getType().isEol()) { + // skip until we find init variable + nextToken(); + } + if (accept(TokenType.initVariable)) { + while (!token.getType().isWhitespace() && !token.getType().isEol()) { + nextToken(); + } + expect(TokenType.whiteSpace); + nextToken(); + expect(TokenType.initOperator); + nextToken(); + expect(TokenType.whiteSpace); + return true; + } + + return false; + } + + protected void prepareInitBlocks() { + List<SimpleNode> answer = new ArrayList<>(); + for (int i = 1; i < nodes.size() - 2; i++) { + SimpleNode token = nodes.get(i); + if (token instanceof InitExpression ie) { + SimpleNode prev = nodes.get(i - 1); + SimpleNode next = nodes.get(i + 1); + ie.acceptLeftNode(prev); + ie.acceptRightNode(next); + answer.add(ie); + + // remember which init variables we have created + if (prev instanceof LiteralNode ln) { + String key = StringHelper.after(ln.getText(), "$$"); + if (key != null) { + key = key.trim(); + initKeys.add(key); + } + } + } + } + nodes.clear(); + nodes.addAll(answer); + } + +} 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 a0f674adaa26..0f2143e797d9 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 @@ -53,6 +53,7 @@ import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.language.simple.types.TokenType; import org.apache.camel.support.ExpressionToPredicateAdapter; import org.apache.camel.support.builder.PredicateBuilder; +import org.apache.camel.util.StringHelper; import static org.apache.camel.support.ObjectHelper.isFloatingNumber; import static org.apache.camel.support.ObjectHelper.isNumber; @@ -82,8 +83,25 @@ public class SimplePredicateParser extends BaseSimpleParser { public Predicate parsePredicate() { try { + // parse init block + SimpleInitBlockParser initParser + = new SimpleInitBlockParser(camelContext, expression, allowEscape, cacheExpression); + Expression init = initParser.parseExpression(); + if (init != null && SimpleTokenizer.hasInitBlock(originalExpression)) { + this.expression = StringHelper.after(originalExpression, SimpleTokenizer.INIT_END); + // use $$key as local variable + for (String key : initParser.getInitKeys()) { + this.expression = this.expression.replace("$$" + key, "${variable." + key + "}"); + } + } + parseTokens(); - return doParsePredicate(); + Predicate pre = doParsePredicate(); + // include init block in expression + if (init != null) { + pre = PredicateBuilder.and(PredicateBuilder.alwaysTrue(init), pre); + } + return pre; } catch (SimpleParserException e) { // catch parser exception and turn that into a syntax exceptions throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java index 0258ce18b8ff..5d6b875e7177 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleTokenizer.java @@ -16,6 +16,8 @@ */ package org.apache.camel.language.simple; +import java.util.concurrent.atomic.AtomicBoolean; + import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.language.simple.types.SimpleTokenType; import org.apache.camel.language.simple.types.TokenType; @@ -27,15 +29,21 @@ import org.apache.camel.util.ObjectHelper; public final class SimpleTokenizer { // keep this number in sync with tokens list - private static final int NUMBER_OF_TOKENS = 50; + private static final int NUMBER_OF_TOKENS = 52; private static final SimpleTokenType[] KNOWN_TOKENS = new SimpleTokenType[NUMBER_OF_TOKENS]; - // optimise to be able to quick check for start functions + // optimize to be able to quick check for start functions private static final String[] FUNCTION_START = new String[] { "${", "$simple{" }; - // optimise to be able to quick check for end function + // optimize to be able to quick check for end function private static final String FUNCTION_END = "}"; + // TODO: what symbols to use for init block? + // optimize to be able to quick check for init block + public static final String INIT_START = "$init{"; + // optimize to be able to quick check for init block + public static final String INIT_END = "}init$"; + static { // add known tokens KNOWN_TOKENS[0] = new SimpleTokenType(TokenType.functionStart, FUNCTION_START[0]); @@ -96,16 +104,35 @@ public final class SimpleTokenizer { KNOWN_TOKENS[47] = new SimpleTokenType(TokenType.logicalOperator, "&&"); KNOWN_TOKENS[48] = new SimpleTokenType(TokenType.logicalOperator, "||"); + // init + KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.initOperator, ":="); + KNOWN_TOKENS[50] = new SimpleTokenType(TokenType.initVariable, "$$"); + //binary operator // it is added as the last item because unary -- has the priority // if unary not found it is highly possible - operator is run into. - KNOWN_TOKENS[49] = new SimpleTokenType(TokenType.minusValue, "-"); + KNOWN_TOKENS[51] = new SimpleTokenType(TokenType.minusValue, "-"); } + static final AtomicBoolean INIT_MODE = new AtomicBoolean(); + private SimpleTokenizer() { // static methods } + /** + * Does the expression include a simple init block. + * + * @param expression the expression + * @return <tt>true</tt> if init block exists + */ + public static boolean hasInitBlock(String expression) { + if (expression != null) { + return expression.startsWith(INIT_START) && expression.contains(INIT_END); + } + return false; + } + /** * Does the expression include a simple function. * @@ -182,6 +209,12 @@ public final class SimpleTokenizer { String text = expression.substring(index); for (int i = 0; i < NUMBER_OF_TOKENS; i++) { SimpleTokenType token = KNOWN_TOKENS[i]; + + // init tokens is only valid if using init mode of tokenizer + boolean init = token.isInit() || token.isInitVariable(); + if (init && !INIT_MODE.get()) { + break; + } if (acceptType(token.getType(), filters) && acceptToken(token, text, expression, index)) { return new SimpleToken(token, index); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitExpression.java new file mode 100644 index 000000000000..24858d96a743 --- /dev/null +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/InitExpression.java @@ -0,0 +1,145 @@ +/* + * 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.language.simple.ast; + +import org.apache.camel.CamelContext; +import org.apache.camel.Exchange; +import org.apache.camel.Expression; +import org.apache.camel.language.simple.BaseSimpleParser; +import org.apache.camel.language.simple.types.InitOperatorType; +import org.apache.camel.language.simple.types.SimpleParserException; +import org.apache.camel.language.simple.types.SimpleToken; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.StringQuoteHelper; + +/** + * Represents other init block expression in the AST. + */ +public class InitExpression extends BaseSimpleNode { + + private final InitOperatorType operator; + private SimpleNode left; + private SimpleNode right; + + public InitExpression(SimpleToken token) { + super(token); + operator = InitOperatorType.asOperator(token.getText()); + } + + @Override + public String toString() { + return left + " " + token.getText() + " " + right; + } + + public boolean acceptLeftNode(SimpleNode lef) { + this.left = lef; + return true; + } + + public boolean acceptRightNode(SimpleNode right) { + this.right = right; + return true; + } + + public InitOperatorType getOperator() { + return operator; + } + + public SimpleNode getLeft() { + return left; + } + + public SimpleNode getRight() { + return right; + } + + @Override + public Expression createExpression(CamelContext camelContext, String expression) { + ObjectHelper.notNull(left, "left node", this); + ObjectHelper.notNull(right, "right node", this); + + // the expression parser does not parse literal text into single/double quote tokens + // so we need to manually remove leading quotes from the literal text when using the other operators + final Expression leftExp = left.createExpression(camelContext, expression); + if (right instanceof LiteralExpression le) { + String text = le.getText(); + String changed = StringHelper.removeLeadingAndEndingQuotes(text); + if (!changed.equals(text)) { + le.replaceText(changed); + } + } + final Expression rightExp = right.createExpression(camelContext, expression); + + if (operator == InitOperatorType.ASSIGNMENT) { + return createAssignmentExpression(camelContext, leftExp, rightExp); + } + + throw new SimpleParserException("Unknown other operator " + operator, token.getIndex()); + } + + private Expression createAssignmentExpression( + final CamelContext camelContext, final Expression leftExp, final Expression rightExp) { + return new Expression() { + @Override + public <T> T evaluate(Exchange exchange, Class<T> type) { + String name = leftExp.evaluate(exchange, String.class); + name = name.trim(); + name = StringHelper.after(name, "$$", name); + Object value = rightExp.evaluate(exchange, Object.class); + exchange.setVariable(name, value); + return null; + } + + @Override + public String toString() { + return left + " " + token.getText() + " " + right; + } + }; + } + + @Override + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleParser.CODE_START + doCreateCode(camelContext, expression) + BaseSimpleParser.CODE_END; + } + + private String doCreateCode(CamelContext camelContext, String expression) throws SimpleParserException { + ObjectHelper.notNull(left, "left node", this); + ObjectHelper.notNull(right, "right node", this); + + // the expression parser does not parse literal text into single/double quote tokens + // so we need to manually remove leading quotes from the literal text when using the other operators + final String leftExp = left.createCode(camelContext, expression); + if (right instanceof LiteralExpression le) { + String text = le.getText(); + // must be in double quotes to be a String type + String changed = StringHelper.removeLeadingAndEndingQuotes(text); + changed = StringQuoteHelper.doubleQuote(changed); + if (!changed.equals(text)) { + le.replaceText(changed); + } + } + final String rightExp = right.createCode(camelContext, expression); + + if (operator == InitOperatorType.ASSIGNMENT) { + return "initAssignment(exchange, " + leftExp + ", " + rightExp + ")"; + } + + throw new SimpleParserException("Unknown other operator " + operator, token.getIndex()); + } + +} diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java similarity index 60% copy from core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java copy to core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java index e737ab809b09..1ebccdd6a799 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/InitOperatorType.java @@ -17,25 +17,29 @@ package org.apache.camel.language.simple.types; /** - * Classifications of known token types. + * Types of init operators supported */ -public enum TokenType { +public enum InitOperatorType { - whiteSpace, - character, - booleanValue, - numericValue, - nullValue, - singleQuote, - doubleQuote, - minusValue, - escape, - functionStart, - functionEnd, - binaryOperator, - otherOperator, - unaryOperator, - logicalOperator, - eol + ASSIGNMENT; + + public static InitOperatorType asOperator(String text) { + if (":=".equals(text)) { + return ASSIGNMENT; + } + throw new IllegalArgumentException("Operator not supported: " + text); + } + + public static String getOperatorText(InitOperatorType operator) { + if (operator == ASSIGNMENT) { + return ":="; + } + return ""; + } + + @Override + public String toString() { + return getOperatorText(this); + } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java index 7663e1d21cd3..4573597f63e6 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/SimpleTokenType.java @@ -124,6 +124,20 @@ public final class SimpleTokenType { return type == TokenType.logicalOperator; } + /** + * Whether the type is init operator + */ + public boolean isInit() { + return type == TokenType.initOperator; + } + + /** + * Whether the type is init variable + */ + public boolean isInitVariable() { + return type == TokenType.initVariable; + } + /** * Whether the type is a null value */ diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java index e737ab809b09..46e16a57e687 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/types/TokenType.java @@ -36,6 +36,8 @@ public enum TokenType { otherOperator, unaryOperator, logicalOperator, + initOperator, + initVariable, eol } diff --git a/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java new file mode 100644 index 000000000000..5867a0655656 --- /dev/null +++ b/core/camel-core/src/test/java/org/apache/camel/language/simple/SimpleInitBlockTest.java @@ -0,0 +1,77 @@ +/* + * 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.language.simple; + +import org.apache.camel.LanguageTestSupport; +import org.junit.jupiter.api.Test; + +public class SimpleInitBlockTest extends LanguageTestSupport { + + private static final String INIT = """ + $init{ + // this is a java like comment + $$sum := ${sum(${header.lines},100)} + + $$sku := ${iif(${body} contains 'Camel',123,999)} + ## and here is also a yaml like comment + }init$ + orderId=$$sku,total=$$sum + """; + + private static final String INIT2 = """ + $init{ + // this is a java like comment + $$sum := ${sum(${header.lines},100)} + + $$sku := ${iif(${body} contains 'Camel',123,999)} + ## and here is also a yaml like comment + }init$ + $$sum > 200 && $$sku != 999 + """; + + @Test + public void testInitBlockExpression() throws Exception { + exchange.getMessage().setBody("Hello Camel"); + exchange.getMessage().setHeader("lines", "75,33"); + + assertExpression(exchange, INIT, "\norderId=123,total=208\n"); + } + + @Test + public void testInitBlockPredicate() throws Exception { + exchange.getMessage().setBody("Hello Camel"); + exchange.getMessage().setHeader("lines", "75,33"); + assertPredicate(exchange, INIT2, true); + + exchange.getMessage().setBody("Hello Camel"); + exchange.getMessage().setHeader("lines", "3,5"); + assertPredicate(exchange, INIT2, false); + + exchange.getMessage().setBody("Hello World"); + exchange.getMessage().setHeader("lines", "75,99"); + assertPredicate(exchange, INIT2, false); + + exchange.getMessage().setBody("Hello World"); + exchange.getMessage().setHeader("lines", "3,5"); + assertPredicate(exchange, INIT2, false); + } + + @Override + protected String getLanguageName() { + return "simple"; + } +} diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java index 669f05ab5a1f..84d9ba670f14 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java @@ -2278,6 +2278,31 @@ public class ExpressionBuilder { }; } + public static Expression eval(List<Expression> expressions) { + return new ExpressionAdapter() { + @Override + public void init(CamelContext context) { + super.init(context); + for (Expression exp : expressions) { + exp.init(context); + } + } + + @Override + public Object evaluate(Exchange exchange) { + for (Expression exp : expressions) { + exp.evaluate(exchange, Object.class); + } + return null; + } + + @Override + public String toString() { + return "eval"; + } + }; + } + /** * Returns an Expression for the inbound message id */ diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java index d87ee46bdc10..213a91ab7d29 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/PredicateBuilder.java @@ -37,6 +37,17 @@ import static org.apache.camel.util.ObjectHelper.notNull; * A helper class for working with predicates */ public class PredicateBuilder { + + /** + * Executes the expression and always return true + */ + public static Predicate alwaysTrue(final Expression expression) { + return exchange -> { + expression.evaluate(exchange, Object.class); + return true; + }; + } + /** * Converts the given expression into an {@link Predicate} */
