This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch between in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8822d4273f208eb5a4ddf47c03abb6d6f948af69 Author: Claus Ibsen <[email protected]> AuthorDate: Thu Jan 15 21:57:45 2026 +0100 CAMEL-22762: Add substringBetween function to simple language. Make more functions able to supported inlined functions. --- .../org/apache/camel/catalog/languages/simple.json | 53 +++++++------- .../language/csimple/joor/OriginalSimpleTest.java | 80 +++++++++++++++++++++ .../org/apache/camel/language/simple/simple.json | 53 +++++++------- .../camel/language/csimple/CSimpleHelper.java | 10 +++ .../camel/language/simple/SimpleConstants.java | 6 +- .../language/simple/SimpleExpressionBuilder.java | 60 ++++++++++++++++ .../simple/ast/SimpleFunctionExpression.java | 81 ++++++++++++++++------ .../apache/camel/language/simple/SimpleTest.java | 81 +++++++++++++++++++++- 8 files changed, 347 insertions(+), 77 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json index dc9b059cb0ae..747358c55f1b 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/languages/simple.json @@ -72,31 +72,32 @@ "substring(head,tail)": { "index": 46, "kind": "function", "displayName": "Substring", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped fr [...] "substringBefore(exp,before)": { "index": 47, "kind": "function", "displayName": "Substring Before", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes before. Returns null if nothing comes before.", "ognl": false, "suffix": "}" }, "substringAfter(exp,before)": { "index": 48, "kind": "function", "displayName": "Substring After", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes after. Returns null if nothing comes after.", "ognl": false, "suffix": "}" }, - "random(min,max)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" }, - "skip(num)": { "index": 50, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of [...] - "convertTo(exp,type)": { "index": 51, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" }, - "trim(exp)": { "index": 52, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" }, - "length(exp)": { "index": 53, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" }, - "size(exp)": { "index": 54, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" }, - "uppercase(exp)": { "index": 55, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" }, - "lowercase(exp)": { "index": 56, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" }, - "concat(exp,exp,separator)": { "index": 57, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" }, - "collate(num)": { "index": 58, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "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\/ba [...] - "join(separator,prefix,exp)": { "index": 59, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default. [...] - "messageHistory(boolean)": { "index": 60, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...] - "uuid(type)": { "index": 61, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...] - "hash(exp,algorithm)": { "index": 62, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" }, - "empty(type)": { "index": 63, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...] - "iif(predicate,trueExp,falseExp)": { "index": 64, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" }, - "list(val...)": { "index": 65, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" }, - "map(key1,value1,...)": { "index": 66, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" }, - "attachments": { "index": 67, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, - "attachments.size": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, - "attachmentContentAsText": { "index": 69, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, - "attachmentContent": { "index": 70, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, - "attachmentContentAs(type)": { "index": 71, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name)": { "index": 72, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name,type)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachment(key)": { "index": 74, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } + "substringBetween(exp,after,before)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" }, + "random(min,max)": { "index": 50, "kind": "function", "displayName": "Random", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that are between after and before. Returns null if nothing comes between.", "ognl": false, "suffix": "}" }, + "skip(num)": { "index": 51, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of [...] + "convertTo(exp,type)": { "index": 52, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" }, + "trim(exp)": { "index": 53, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" }, + "length(exp)": { "index": 54, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" }, + "size(exp)": { "index": 55, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" }, + "uppercase(exp)": { "index": 56, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" }, + "lowercase(exp)": { "index": 57, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" }, + "concat(exp,exp,separator)": { "index": 58, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" }, + "collate(num)": { "index": 59, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "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\/ba [...] + "join(separator,prefix,exp)": { "index": 60, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default. [...] + "messageHistory(boolean)": { "index": 61, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...] + "uuid(type)": { "index": 62, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...] + "hash(exp,algorithm)": { "index": 63, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" }, + "empty(type)": { "index": 64, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...] + "iif(predicate,trueExp,falseExp)": { "index": 65, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" }, + "list(val...)": { "index": 66, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" }, + "map(key1,value1,...)": { "index": 67, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" }, + "attachments": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, + "attachments.size": { "index": 69, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, + "attachmentContentAsText": { "index": 70, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, + "attachmentContent": { "index": 71, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, + "attachmentContentAs(type)": { "index": 72, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name,type)": { "index": 74, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachment(key)": { "index": 75, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } } } 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 a46a23f10f35..cb77d0af7756 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 @@ -1879,6 +1879,58 @@ public class OriginalSimpleTest extends LanguageTestSupport { assertEquals("G", chunk3.get(0)); } + @Test + public void testCollateDynamic() { + List<Object> data = new ArrayList<>(); + data.add("A"); + data.add("B"); + data.add("C"); + data.add("D"); + data.add("E"); + data.add("F"); + data.add("G"); + exchange.getIn().setBody(data); + + exchange.getIn().setHeader("num", 3); + + Iterator it = (Iterator) evaluateExpression("${collate(${header.num})}", null); + List chunk = (List) it.next(); + List chunk2 = (List) it.next(); + List chunk3 = (List) it.next(); + assertFalse(it.hasNext()); + + assertEquals(3, chunk.size()); + assertEquals(3, chunk2.size()); + assertEquals(1, chunk3.size()); + + assertEquals("A", chunk.get(0)); + assertEquals("B", chunk.get(1)); + assertEquals("C", chunk.get(2)); + assertEquals("D", chunk2.get(0)); + assertEquals("E", chunk2.get(1)); + assertEquals("F", chunk2.get(2)); + assertEquals("G", chunk3.get(0)); + } + + @Test + public void testSkip() { + List<Object> data = new ArrayList<>(); + data.add("A"); + data.add("B"); + data.add("C"); + data.add("D"); + data.add("E"); + data.add("F"); + exchange.getIn().setBody(data); + + Iterator it = (Iterator) evaluateExpression("${skip(2)}", null); + assertEquals("C", it.next()); + assertEquals("D", it.next()); + assertEquals("E", it.next()); + assertEquals("F", it.next()); + assertFalse(it.hasNext()); + } + @Test public void testRandomExpression() { int min = 1; @@ -2396,6 +2448,34 @@ public class OriginalSimpleTest extends LanguageTestSupport { assertEquals(" World", s); } + @Test + public void testSubstringBetween() { + exchange.getMessage().setBody("Hello big great World"); + + Expression expression = context.resolveLanguage("csimple").createExpression("${substringBetween('Hello','World')}"); + String s = expression.evaluate(exchange, String.class); + assertEquals(" big great ", s); + + expression = context.resolveLanguage("csimple").createExpression("${substringBetween('Hello ',' World')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("big great", s); + + expression = context.resolveLanguage("csimple").createExpression("${substringBetween(${body},'big ',' World')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("great", s); + + expression = context.resolveLanguage("csimple").createExpression("${substringBetween('Hello','Unknown')}"); + s = expression.evaluate(exchange, String.class); + assertNull(s); + + exchange.getMessage().setHeader("place", "Hello"); + exchange.getMessage().setHeader("place2", "great"); + expression = context.resolveLanguage("csimple") + .createExpression("${substringBetween(${body},${header.place},${header.place2})}"); + s = expression.evaluate(exchange, String.class); + assertEquals(" big ", s); + } + @Test public void testTrim() { exchange.getMessage().setBody(" Hello World "); diff --git a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json index dc9b059cb0ae..747358c55f1b 100644 --- a/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json +++ b/core/camel-core-languages/src/generated/resources/META-INF/org/apache/camel/language/simple/simple.json @@ -72,31 +72,32 @@ "substring(head,tail)": { "index": 46, "kind": "function", "displayName": "Substring", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression. If only one positive number, then the returned string is clipped from the beginning. If only one negative number, then the returned string is clipped fr [...] "substringBefore(exp,before)": { "index": 47, "kind": "function", "displayName": "Substring Before", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes before. Returns null if nothing comes before.", "ognl": false, "suffix": "}" }, "substringAfter(exp,before)": { "index": 48, "kind": "function", "displayName": "Substring After", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that comes after. Returns null if nothing comes after.", "ognl": false, "suffix": "}" }, - "random(min,max)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" }, - "skip(num)": { "index": 50, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of [...] - "convertTo(exp,type)": { "index": 51, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" }, - "trim(exp)": { "index": 52, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" }, - "length(exp)": { "index": 53, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" }, - "size(exp)": { "index": 54, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" }, - "uppercase(exp)": { "index": 55, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" }, - "lowercase(exp)": { "index": 56, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" }, - "concat(exp,exp,separator)": { "index": 57, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" }, - "collate(num)": { "index": 58, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "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\/ba [...] - "join(separator,prefix,exp)": { "index": 59, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default. [...] - "messageHistory(boolean)": { "index": 60, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...] - "uuid(type)": { "index": 61, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...] - "hash(exp,algorithm)": { "index": 62, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" }, - "empty(type)": { "index": 63, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...] - "iif(predicate,trueExp,falseExp)": { "index": 64, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" }, - "list(val...)": { "index": 65, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" }, - "map(key1,value1,...)": { "index": 66, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" }, - "attachments": { "index": 67, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, - "attachments.size": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, - "attachmentContentAsText": { "index": 69, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, - "attachmentContent": { "index": 70, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, - "attachmentContentAs(type)": { "index": 71, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name)": { "index": 72, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, - "attachmentHeader(key,name,type)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, - "attachment(key)": { "index": 74, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } + "substringBetween(exp,after,before)": { "index": 49, "kind": "function", "displayName": "Generate Random Number", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a random number between min (included) and max (excluded).", "ognl": false, "suffix": "}" }, + "random(min,max)": { "index": 50, "kind": "function", "displayName": "Random", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a substring of the message body\/expression that are between after and before. Returns null if nothing comes between.", "ognl": false, "suffix": "}" }, + "skip(num)": { "index": 51, "kind": "function", "displayName": "Skip First Items from the Message Body", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The skip function iterates the message body and skips the first number of items. This can be used with the Splitter EIP to split a message body and skip the first N number of [...] + "convertTo(exp,type)": { "index": 52, "kind": "function", "displayName": "Convert To", "group": "function", "label": "function", "required": false, "javaType": "", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Converts the message body (or expression) to the specified type.", "ognl": true, "suffix": "}" }, + "trim(exp)": { "index": 53, "kind": "function", "displayName": "Trim", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The trim function trims the message body (or expression) by removing all leading and trailing white spaces.", "ognl": false, "suffix": "}" }, + "length(exp)": { "index": 54, "kind": "function", "displayName": "Length", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The payload length (number of bytes) of the message body (or expression).", "ognl": false, "suffix": "}" }, + "size(exp)": { "index": 55, "kind": "function", "displayName": "Size", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The size of the message body (or expression). If the payload is java.util.Collection or java.util.Map based then the size is the number of elements; otherwise the payload size in bytes.", "ognl": false, "suffix": "}" }, + "uppercase(exp)": { "index": 56, "kind": "function", "displayName": "Uppercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Uppercases the message body (or expression)", "ognl": false, "suffix": "}" }, + "lowercase(exp)": { "index": 57, "kind": "function", "displayName": "Lowercase", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Lowercases the message body (or expression)", "ognl": false, "suffix": "}" }, + "concat(exp,exp,separator)": { "index": 58, "kind": "function", "displayName": "Concat", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Performs a string concat using two expressions (message body as default) with optional separator", "ognl": false, "suffix": "}" }, + "collate(num)": { "index": 59, "kind": "function", "displayName": "Group Message Body into Sub Lists", "group": "function", "label": "function", "required": false, "javaType": "java.util.Iterator", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "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\/ba [...] + "join(separator,prefix,exp)": { "index": 60, "kind": "function", "displayName": "Join", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The join function iterates the message body\/expression and joins the data into a string. The separator is by default a comma. The prefix is optional. The join uses the message body as source by default. [...] + "messageHistory(boolean)": { "index": 61, "kind": "function", "displayName": "Print Message History", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The message history of the current exchange (how it has been routed). This is similar to the route stack-trace message history the error handler logs in case of an unhandled exception. The b [...] + "uuid(type)": { "index": 62, "kind": "function", "displayName": "Generate UUID", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a UUID using the Camel `UuidGenerator`. You can choose between `default`, `classic`, `short` and `simple` as the type. If no type is given, the default is used. It is also possible to use a custom `UuidG [...] + "hash(exp,algorithm)": { "index": 63, "kind": "function", "displayName": "Compute Hash Value", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Returns a hashed value (string in hex decimal) of the message body\/expression using JDK MessageDigest. The algorithm can be SHA-256 (default) or SHA3-256.", "ognl": false, "suffix": "}" }, + "empty(type)": { "index": 64, "kind": "function", "displayName": "Create Empty Object", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Creates a new empty object (decided by type). Use `string` to create an empty String. Use `list` to create an empty `java.util.ArrayList`. Use `map` to create an empty `java.util.LinkedHashMap`.", "ognl": [...] + "iif(predicate,trueExp,falseExp)": { "index": 65, "kind": "function", "displayName": "If Then Else", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "Evaluates the predicate and returns the value of trueExp or falseExp. This function is similar to the ternary operator in Java.", "ognl": false, "suffix": "}" }, + "list(val...)": { "index": 66, "kind": "function", "displayName": "Create List of values", "group": "function", "label": "function", "required": false, "javaType": "java.util.ArrayList", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The list function creates an ArrayList with the given set of values.", "ognl": false, "suffix": "}" }, + "map(key1,value1,...)": { "index": 67, "kind": "function", "displayName": "Create Map of pairs", "group": "function", "label": "function", "required": false, "javaType": "java.util.LinkedHashMap", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The map function creates a LinkedHashMap with the given set of pairs.", "ognl": false, "suffix": "}" }, + "attachments": { "index": 68, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "java.util.Map", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "All the attachments as a Map<String,DataHandler.", "ognl": false, "suffix": "}" }, + "attachments.size": { "index": 69, "kind": "function", "displayName": "Attachments", "group": "function", "label": "function", "required": false, "javaType": "int", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The number of attachments. Is 0 if there are no attachments.", "ognl": false, "suffix": "}" }, + "attachmentContentAsText": { "index": 70, "kind": "function", "displayName": "Attachment Content As Text", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment as text (ie String).", "ognl": false, "suffix": "}" }, + "attachmentContent": { "index": 71, "kind": "function", "displayName": "Attachment Content", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment", "ognl": false, "suffix": "}" }, + "attachmentContentAs(type)": { "index": 72, "kind": "function", "displayName": "Attachment Content As", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The content of the attachment, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name)": { "index": 73, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "String", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name.", "ognl": false, "suffix": "}" }, + "attachmentHeader(key,name,type)": { "index": 74, "kind": "function", "displayName": "Attachment Header", "group": "function", "label": "function", "required": false, "javaType": "Object", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The attachment header with the given name, converted to the given type.", "ognl": false, "suffix": "}" }, + "attachment(key)": { "index": 75, "kind": "function", "displayName": "Attachment", "group": "function", "label": "function", "required": false, "javaType": "jakarta.activation.DataHandler", "prefix": "${", "deprecated": false, "deprecationNote": "", "autowired": false, "secret": false, "description": "The DataHandler for the given attachment.", "ognl": true, "suffix": "}" } } } 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 c393035eea3c..202236b2ca0e 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 @@ -572,6 +572,16 @@ public final class CSimpleHelper { return StringHelper.after(body, after); } + public static String substringBetween(Exchange exchange, Object value, Object after, Object before) { + String body = exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, value); + if (body == null) { + return null; + } + String strAfter = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, after); + String strBefore = exchange.getContext().getTypeConverter().convertTo(String.class, exchange, before); + return StringHelper.between(body, strAfter, strBefore); + } + public static int random(Exchange exchange, Object min, Object max) { int num1 = exchange.getContext().getTypeConverter().tryConvertTo(int.class, exchange, min); int num2 = exchange.getContext().getTypeConverter().tryConvertTo(int.class, exchange, max); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java index 6ba4fa4d8666..505189bba43f 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleConstants.java @@ -183,9 +183,9 @@ public final class SimpleConstants { public static final String SUBSTRING_AFTER = "substringAfter(exp,before)"; @Metadata(description = "Returns a random number between min (included) and max (excluded).", label = "function", javaType = "int", displayName = "Generate Random Number") - public static final String SUBSTRING_BETWEEN = "substringBetween(exp,before,after)"; - @Metadata(description = "Returns a substring of the message body/expression that are between before and after. Returns null if nothing comes between.", - label = "function", javaType = "String") + public static final String SUBSTRING_BETWEEN = "substringBetween(exp,after,before)"; + @Metadata(description = "Returns a substring of the message body/expression that are between after and before. Returns null if nothing comes between.", + label = "function", javaType = "String") public static final String RANDOM = "random(min,max)"; @Metadata(description = "The skip function iterates the message body and skips the first number of items." + " This can be used with the Splitter EIP to split a message body and skip the first N number of items.", 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 ef65aad8acee..b594c7b2e41d 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 @@ -163,6 +163,37 @@ public final class SimpleExpressionBuilder { }; } + /** + * Returns an iterator to collate (iterate) the given expression + */ + public static Expression collateExpression(final String expression, final String group) { + return new ExpressionAdapter() { + private Expression exp; + private Expression num; + + @Override + public void init(CamelContext context) { + exp = context.resolveLanguage("simple").createExpression(expression); + exp.init(context); + num = context.resolveLanguage("simple").createExpression(group); + num.init(context); + } + + @Override + public Object evaluate(Exchange exchange) { + Integer n = num.evaluate(exchange, Integer.class); + exp = ExpressionBuilder.groupIteratorExpression(exp, null, Integer.toString(n), false); + exp.init(exchange.getContext()); + return exp.evaluate(exchange, Object.class); + } + + @Override + public String toString() { + return "collate(" + expression + "," + group + ")"; + } + }; + } + /** * Returns an iterator to skip (iterate) the given expression */ @@ -188,6 +219,35 @@ public final class SimpleExpressionBuilder { }; } + /** + * Returns an iterator to skip (iterate) the given expression + */ + public static Expression skipExpression(final String expression, final String number) { + return new ExpressionAdapter() { + private Expression exp; + private Expression num; + + @Override + public void init(CamelContext context) { + exp = context.resolveLanguage("simple").createExpression(expression); + exp.init(context); + num = context.resolveLanguage("simple").createExpression(number); + num.init(context); + } + + @Override + public Object evaluate(Exchange exchange) { + int n = num.evaluate(exchange, Integer.class); + return skipIteratorExpression(exp, n).evaluate(exchange, Object.class); + } + + @Override + public String toString() { + return "skip(" + expression + "," + number + ")"; + } + }; + } + /** * Trims the given expressions (uses message body if expression is null) */ 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 ac36da491678..2e35068662ac 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 @@ -728,7 +728,7 @@ public class SimpleFunctionExpression extends LiteralExpression { // replace function remainder = ifStartsWithReturnRemainder("replace(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( "Valid syntax: ${replace(from,to)} or ${replace(from,to,expression)} was: " + function, @@ -755,7 +755,7 @@ public class SimpleFunctionExpression extends LiteralExpression { // substring function remainder = ifStartsWithReturnRemainder("substring(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( "Valid syntax: ${substring(num)}, ${substring(num,num)}, or ${substring(num,num,expression)} was: " @@ -780,7 +780,7 @@ public class SimpleFunctionExpression extends LiteralExpression { } remainder = ifStartsWithReturnRemainder("substringBefore(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( "Valid syntax: ${substringBefore(exp)} or ${substringBefore(exp,exp)} was: " @@ -806,7 +806,7 @@ public class SimpleFunctionExpression extends LiteralExpression { } remainder = ifStartsWithReturnRemainder("substringAfter(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( "Valid syntax: ${substringAfter(exp)} or ${substringAfter(exp,exp)} was: " @@ -832,18 +832,18 @@ public class SimpleFunctionExpression extends LiteralExpression { } remainder = ifStartsWithReturnRemainder("substringBetween(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( - "Valid syntax: ${substringBetween(before,after)} or ${substringAfter(exp,before,after)} was: " + "Valid syntax: ${substringBetween(after,before)} or ${substringAfter(exp,after,before)} was: " + function, token.getIndex()); } String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); if (tokens.length < 2 || tokens.length > 3) { throw new SimpleParserException( - "Valid syntax: ${substringBetween(before,after)} or ${substringAfter(exp,before,after)} was: " - + function, + "Valid syntax: ${substringBetween(after,before)} or ${substringAfter(exp,after,before)} was: " + + function, token.getIndex()); } String exp1 = "${body}"; @@ -863,7 +863,7 @@ public class SimpleFunctionExpression extends LiteralExpression { // random function remainder = ifStartsWithReturnRemainder("random(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( "Valid syntax: ${random(min,max)} or ${random(max)} was: " + function, token.getIndex()); @@ -888,26 +888,24 @@ public class SimpleFunctionExpression extends LiteralExpression { throw new SimpleParserException("Valid syntax: ${skip(number)} was: " + function, token.getIndex()); } String exp = "${body}"; - int num = Integer.parseInt(values.trim()); - return SimpleExpressionBuilder.skipExpression(exp, num); + return SimpleExpressionBuilder.skipExpression(exp, values.trim()); } // collate function remainder = ifStartsWithReturnRemainder("collate(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException("Valid syntax: ${collate(group)} was: " + function, token.getIndex()); } String exp = "${body}"; - int num = Integer.parseInt(values.trim()); - return SimpleExpressionBuilder.collateExpression(exp, num); + return SimpleExpressionBuilder.collateExpression(exp, values.trim()); } // join function remainder = ifStartsWithReturnRemainder("join(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); String separator = ","; String prefix = null; String exp = "${body}"; @@ -948,7 +946,7 @@ public class SimpleFunctionExpression extends LiteralExpression { String separator = null; String exp1 = "${body}"; String exp2; - String values = StringHelper.before(remainder, ")"); + String values = StringHelper.beforeLast(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( "Valid syntax: ${concat(exp)} or ${concat(exp,exp)} or ${concat(exp,exp,separator)} was: " + function, @@ -978,6 +976,7 @@ public class SimpleFunctionExpression extends LiteralExpression { if (remainder != null) { String exp = "${body}"; String type; + // do not use beforeLast as this supports OGNL String values = StringHelper.before(remainder, ")"); if (values == null || ObjectHelper.isEmpty(values)) { throw new SimpleParserException( @@ -1014,7 +1013,7 @@ public class SimpleFunctionExpression extends LiteralExpression { remainder = ifStartsWithReturnRemainder("uppercase(", function); if (remainder != null) { String exp = null; - String value = StringHelper.before(remainder, ")"); + String value = StringHelper.beforeLast(remainder, ")"); if (ObjectHelper.isNotEmpty(value)) { exp = StringHelper.removeQuotes(value); } @@ -1024,7 +1023,7 @@ public class SimpleFunctionExpression extends LiteralExpression { remainder = ifStartsWithReturnRemainder("lowercase(", function); if (remainder != null) { String exp = null; - String value = StringHelper.before(remainder, ")"); + String value = StringHelper.beforeLast(remainder, ")"); if (ObjectHelper.isNotEmpty(value)) { exp = StringHelper.removeQuotes(value); } @@ -1035,7 +1034,7 @@ public class SimpleFunctionExpression extends LiteralExpression { remainder = ifStartsWithReturnRemainder("length(", function); if (remainder != null) { String exp = null; - String value = StringHelper.before(remainder, ")"); + String value = StringHelper.beforeLast(remainder, ")"); if (ObjectHelper.isNotEmpty(value)) { exp = StringHelper.removeQuotes(value); } @@ -1045,7 +1044,7 @@ public class SimpleFunctionExpression extends LiteralExpression { remainder = ifStartsWithReturnRemainder("size(", function); if (remainder != null) { String exp = null; - String value = StringHelper.before(remainder, ")"); + String value = StringHelper.beforeLast(remainder, ")"); if (ObjectHelper.isNotEmpty(value)) { exp = StringHelper.removeQuotes(value); } @@ -1079,7 +1078,7 @@ public class SimpleFunctionExpression extends LiteralExpression { // hash function remainder = ifStartsWithReturnRemainder("hash(", function); if (remainder != null) { - String values = StringHelper.before(remainder, ")"); + 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()); @@ -2105,6 +2104,46 @@ public class SimpleFunctionExpression extends LiteralExpression { return "Object value = " + body + ";\n Object after = " + before + ";\n return substringAfter(exchange, value, after);"; } + remainder = ifStartsWithReturnRemainder("substringBetween(", function); + if (remainder != null) { + String values = StringHelper.beforeLast(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${substringBetween(after,before)}, ${substringBetween(exp,after,before)} was: " + + function, + token.getIndex()); + } + String[] tokens = codeSplitSafe(values, ',', true, true); + if (tokens.length < 2 || tokens.length > 3) { + throw new SimpleParserException( + "Valid syntax: ${substringBetween(after,before)}, ${substringBetween(exp,after,before)} 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 body = "body"; + String after; + String before; + if (tokens.length == 3) { + body = tokens[0]; + after = tokens[1]; + before = tokens[2]; + } else { + after = tokens[0]; + before = tokens[1]; + } + return "Object value = " + body + ";\n Object after = " + after + + ";\n Object before = " + before + + ";\n return substringBetween(exchange, value, after, before);"; + } // random function remainder = ifStartsWithReturnRemainder("random(", function); 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 9b281eb35016..81298eaeb1e9 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 @@ -1967,6 +1967,76 @@ public class SimpleTest extends LanguageTestSupport { assertEquals("G", chunk3.get(0)); } + @Test + public void testCollateDynamic() { + List<Object> data = new ArrayList<>(); + data.add("A"); + data.add("B"); + data.add("C"); + data.add("D"); + data.add("E"); + data.add("F"); + data.add("G"); + exchange.getIn().setBody(data); + + exchange.getIn().setHeader("num", 3); + + Iterator it = (Iterator) evaluateExpression("${collate(${header.num})}", null); + List chunk = (List) it.next(); + List chunk2 = (List) it.next(); + List chunk3 = (List) it.next(); + assertFalse(it.hasNext()); + + assertEquals(3, chunk.size()); + assertEquals(3, chunk2.size()); + assertEquals(1, chunk3.size()); + + assertEquals("A", chunk.get(0)); + assertEquals("B", chunk.get(1)); + assertEquals("C", chunk.get(2)); + assertEquals("D", chunk2.get(0)); + assertEquals("E", chunk2.get(1)); + assertEquals("F", chunk2.get(2)); + assertEquals("G", chunk3.get(0)); + } + + @Test + public void testSkip() { + List<Object> data = new ArrayList<>(); + data.add("A"); + data.add("B"); + data.add("C"); + data.add("D"); + data.add("E"); + data.add("F"); + exchange.getIn().setBody(data); + + Iterator it = (Iterator) evaluateExpression("${skip(2)}", null); + assertEquals("C", it.next()); + assertEquals("D", it.next()); + assertEquals("E", it.next()); + assertEquals("F", it.next()); + assertFalse(it.hasNext()); + } + + @Test + public void testSkipDynamic() { + List<Object> data = new ArrayList<>(); + data.add("A"); + data.add("B"); + data.add("C"); + data.add("D"); + data.add("E"); + data.add("F"); + exchange.getIn().setBody(data); + exchange.getIn().setHeader("num", 4); + + Iterator it = (Iterator) evaluateExpression("${skip(${header.num})}", null); + assertEquals("E", it.next()); + assertEquals("F", it.next()); + assertFalse(it.hasNext()); + } + @Test public void testJoinBody() { List<Object> data = new ArrayList<>(); @@ -2564,6 +2634,10 @@ public class SimpleTest extends LanguageTestSupport { s = expression.evaluate(exchange, String.class); assertEquals("Hello", s); + expression = context.resolveLanguage("simple").createExpression("${trim(${substringBefore('World')})}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello", s); + expression = context.resolveLanguage("simple").createExpression("${substringBefore(${body},'World')}"); s = expression.evaluate(exchange, String.class); assertEquals("Hello ", s); @@ -2594,6 +2668,10 @@ public class SimpleTest extends LanguageTestSupport { s = expression.evaluate(exchange, String.class); assertEquals(" World", s); + expression = context.resolveLanguage("simple").createExpression("${trim(${substringAfter(${body},'Hello')})}"); + s = expression.evaluate(exchange, String.class); + assertEquals("World", s); + expression = context.resolveLanguage("simple").createExpression("${substringAfter('Unknown')}"); s = expression.evaluate(exchange, String.class); assertNull(s); @@ -2630,7 +2708,8 @@ public class SimpleTest extends LanguageTestSupport { exchange.getMessage().setHeader("place", "Hello"); exchange.getMessage().setHeader("place2", "great"); - expression = context.resolveLanguage("simple").createExpression("${substringBetween(${body},${header.place},${header.place2})}"); + expression = context.resolveLanguage("simple") + .createExpression("${substringBetween(${body},${header.place},${header.place2})}"); s = expression.evaluate(exchange, String.class); assertEquals(" big ", s); }
