This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch con in repository https://gitbox.apache.org/repos/asf/camel.git
commit c3a8094809739855e0b5a080c05797b22a6b6a14 Author: Claus Ibsen <[email protected]> AuthorDate: Mon Jan 12 14:50:59 2026 +0100 CAMEL-22841: camel-core - Add concat function to simple language --- .../org/apache/camel/catalog/languages/simple.json | 35 +++++----- .../language/csimple/joor/OriginalSimpleTest.java | 30 ++++++++ .../org/apache/camel/language/simple/simple.json | 35 +++++----- .../modules/languages/pages/simple-language.adoc | 2 + .../camel/language/csimple/CSimpleHelper.java | 12 ++++ .../camel/language/simple/SimpleConstants.java | 3 + .../language/simple/SimpleExpressionBuilder.java | 34 ++++++++++ .../simple/ast/SimpleFunctionExpression.java | 79 ++++++++++++++++++++++ .../apache/camel/language/simple/SimpleTest.java | 30 ++++++++ 9 files changed, 226 insertions(+), 34 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 c92baffeceb8..9fdcc0022596 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 @@ -73,22 +73,23 @@ "trim(exp)": { "index": 48, "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": "}" }, "uppercase(exp)": { "index": 49, "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": 50, "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": "}" }, - "collate(num)": { "index": 51, "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": 52, "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": 53, "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": 54, "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": 55, "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": 56, "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": 57, "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": 58, "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": 59, "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": 60, "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": 61, "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": 62, "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": 63, "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": 64, "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": 65, "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": 66, "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": 67, "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": "}" } + "concat(exp,exp,separator)": { "index": 51, "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": 52, "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": 53, "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": 54, "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": 55, "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": 56, "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": 57, "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": 58, "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": 59, "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": 60, "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": 61, "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": 62, "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": 63, "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": 64, "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": 65, "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": 66, "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": 67, "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": 68, "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 eafe538d47f9..b3fad07f87b8 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 @@ -2262,6 +2262,36 @@ public class OriginalSimpleTest extends LanguageTestSupport { assertEquals("Carlsberg", s); } + @Test + public void testConcat() { + exchange.getMessage().setBody("Hello"); + + Expression expression = context.resolveLanguage("csimple").createExpression("${concat(' World')}"); + String s = expression.evaluate(exchange, String.class); + assertEquals("Hello World", s); + + expression = context.resolveLanguage("csimple").createExpression("${concat(${body}, ' World')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello World", s); + + expression = context.resolveLanguage("csimple").createExpression("${concat(${body}, 'World', '_')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello_World", s); + + expression = context.resolveLanguage("csimple").createExpression("${concat('World ', ${body})}"); + s = expression.evaluate(exchange, String.class); + assertEquals("World Hello", s); + + exchange.getMessage().setHeader("beer", "Carlsberg"); + expression = context.resolveLanguage("csimple").createExpression("${concat(${header.beer})}"); + s = expression.evaluate(exchange, String.class); + assertEquals("HelloCarlsberg", s); + + expression = context.resolveLanguage("csimple").createExpression("${concat(${body}, ${header.beer}, ' ')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello Carlsberg", s); + } + @Test public void testUppercase() { 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 c92baffeceb8..9fdcc0022596 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 @@ -73,22 +73,23 @@ "trim(exp)": { "index": 48, "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": "}" }, "uppercase(exp)": { "index": 49, "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": 50, "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": "}" }, - "collate(num)": { "index": 51, "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": 52, "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": 53, "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": 54, "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": 55, "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": 56, "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": 57, "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": 58, "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": 59, "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": 60, "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": 61, "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": 62, "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": 63, "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": 64, "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": 65, "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": 66, "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": 67, "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": "}" } + "concat(exp,exp,separator)": { "index": 51, "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": 52, "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": 53, "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": 54, "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": 55, "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": 56, "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": 57, "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": 58, "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": 59, "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": 60, "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": 61, "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": 62, "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": 63, "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": 64, "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": 65, "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": 66, "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": 67, "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": 68, "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/docs/modules/languages/pages/simple-language.adoc b/core/camel-core-languages/src/main/docs/modules/languages/pages/simple-language.adoc index 567a2aefa915..177b7bc0de58 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 @@ -292,6 +292,8 @@ log sensitive data from the message itself. |trim(exp) |String |The trim function trims the message body (or expression) by removing all leading and trailing white spaces. +|concat(exp,exp,separator) |String |Performs a string concat using two expressions (message body as default) with optional separator + |uppercase(exp) |String |Uppercases the message body (or expression) |lowercase(exp) |String |Lowercases the message body (or expression) 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 a4dd1f47ed52..e4a3e0ed4a81 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 @@ -788,6 +788,18 @@ public final class CSimpleHelper { return body; } + public static String concat(Exchange exchange, Object right, Object left, Object separator) { + String val1 = exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, right); + String val2 = exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, left); + String sep = exchange.getContext().getTypeConverter().tryConvertTo(String.class, exchange, separator); + + if (val1 != null && val2 != null) { + return val1 + (sep != null ? sep : "") + val2; + } else { + return val1 != null ? val1 : val2; + } + } + public static String uppercase(Exchange exchange, Object value) { String body; if (value != null) { 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 0a4be1b09828..0f27b6b193bc 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 @@ -189,6 +189,9 @@ public final class SimpleConstants { @Metadata(description = "Lowercases the message body (or expression)", label = "function", javaType = "String", displayName = "Lowercase") public static final String LOWERCASE = "lowercase(exp)"; + @Metadata(description = "Performs a string concat using two expressions (message body as default) with optional separator", + label = "function", javaType = "String", displayName = "Concat") + public static final String CONCAT = "concat(exp,exp,separator)"; @Metadata(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/batch" + " the split sub message into a group of N sub lists.", 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 4e6b2bfdd07d..02f807b828f9 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 @@ -221,6 +221,40 @@ public final class SimpleExpressionBuilder { }; } + /** + * String concats the two expressions. + */ + public static Expression concatExpression(final String right, final String left, String separator) { + return new ExpressionAdapter() { + private Expression exp1; + private Expression exp2; + + @Override + public void init(CamelContext context) { + exp1 = context.resolveLanguage("simple").createExpression(right); + exp1.init(context); + exp2 = context.resolveLanguage("simple").createExpression(left); + exp2.init(context); + } + + @Override + public Object evaluate(Exchange exchange) { + String value1 = exp1.evaluate(exchange, String.class); + String value2 = exp2.evaluate(exchange, String.class); + if (value1 != null && value2 != null) { + return value1 + (separator != null ? separator : "") + value2; + } else { + return value1 != null ? value1 : value2; + } + } + + @Override + public String toString() { + return "concat(" + right + "," + left + ")"; + } + }; + } + /** * Uppercases 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 8c25c183fcc2..7c3658025fbe 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 @@ -859,6 +859,37 @@ public class SimpleFunctionExpression extends LiteralExpression { return SimpleExpressionBuilder.trimExpression(exp); } + // concat function + remainder = ifStartsWithReturnRemainder("concat(", function); + if (remainder != null) { + String separator = null; + String exp1 = "${body}"; + String exp2; + String values = StringHelper.before(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, + token.getIndex()); + } + if (values.contains(",")) { + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', true, true); + if (tokens.length > 3) { + throw new SimpleParserException( + "Valid syntax: ${concat(exp)} or ${concat(exp,exp)} or ${concat(exp,exp,separator)} was: " + + function, + token.getIndex()); + } + exp1 = StringHelper.removeQuotes(tokens[0]); + exp2 = StringHelper.removeQuotes(tokens[1]); + if (tokens.length == 3) { + separator = StringHelper.removeQuotes(tokens[2]); + } + } else { + exp2 = StringHelper.removeQuotes(values.trim()); + } + return SimpleExpressionBuilder.concatExpression(exp1, exp2, separator); + } + // uppercase function remainder = ifStartsWithReturnRemainder("uppercase(", function); if (remainder != null) { @@ -1948,6 +1979,54 @@ public class SimpleFunctionExpression extends LiteralExpression { return "Object o = " + exp + ";\n return trim(exchange, o);"; } + // concat function + remainder = ifStartsWithReturnRemainder("concat(", function); + if (remainder != null) { + String separator = "null"; + String exp1 = "body"; + String exp2; + 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, + token.getIndex()); + } + if (values.contains(",")) { + String[] tokens = codeSplitSafe(values, ',', true, true); + if (tokens.length > 3) { + throw new SimpleParserException( + "Valid syntax: ${concat(exp)} or ${concat(exp,exp)} or ${concat(exp,exp,separator)} 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; + } + } + if (tokens.length == 1) { + exp2 = tokens[0]; + } else { + exp1 = tokens[0]; + exp2 = tokens[1]; + } + if (tokens.length == 3) { + separator = tokens[2]; + } + } else { + String s = values.trim(); + s = StringHelper.removeLeadingAndEndingQuotes(s); + s = StringQuoteHelper.doubleQuote(s); + exp2 = s; + } + return "Object right = " + exp1 + ";\n Object left = " + exp2 + ";\n " + "Object separator = " + + separator + ";\n return concat(exchange, right, left, separator);"; + } + // uppercase function remainder = ifStartsWithReturnRemainder("uppercase(", function); if (remainder != null) { 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 501b94b47a08..ecb88e21b8fa 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 @@ -2419,6 +2419,36 @@ public class SimpleTest extends LanguageTestSupport { assertEquals("Carlsberg", s); } + @Test + public void testConcat() { + exchange.getMessage().setBody("Hello"); + + Expression expression = context.resolveLanguage("simple").createExpression("${concat(' World')}"); + String s = expression.evaluate(exchange, String.class); + assertEquals("Hello World", s); + + expression = context.resolveLanguage("simple").createExpression("${concat(${body}, ' World')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello World", s); + + expression = context.resolveLanguage("simple").createExpression("${concat(${body}, 'World', '_')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello_World", s); + + expression = context.resolveLanguage("simple").createExpression("${concat('World ', ${body})}"); + s = expression.evaluate(exchange, String.class); + assertEquals("World Hello", s); + + exchange.getMessage().setHeader("beer", "Carlsberg"); + expression = context.resolveLanguage("simple").createExpression("${concat(${header.beer})}"); + s = expression.evaluate(exchange, String.class); + assertEquals("HelloCarlsberg", s); + + expression = context.resolveLanguage("simple").createExpression("${concat(${body}, ${header.beer}, ' ')}"); + s = expression.evaluate(exchange, String.class); + assertEquals("Hello Carlsberg", s); + } + @Test public void testUppercase() { exchange.getMessage().setBody("Hello World");
