This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch sa in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8cb9fd6242f825b616f8b59966661f8de01ea604 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Tue Feb 4 15:51:23 2025 +0100 CAMEL-21703: Add external functions to simple language such as camel-attachments --- components/camel-attachments/pom.xml | 11 + .../simple-function-factory/camel-attachments | 2 + .../src/main/docs/attachments.adoc | 2 +- .../attachment/AttachmentExpressionBuilder.java | 225 +++++++++++++++ .../camel/attachment/CSimpleAttachmentHelper.java | 95 +++++++ .../camel/attachment/CamelFileDataSource.java | 7 + .../attachment/NoSuchAttachmentException.java | 62 +++++ .../camel/attachment/SimpleAttachmentFunction.java | 301 +++++++++++++++++++++ components/camel-attachments/src/test/data/123.txt | 1 + .../camel/attachment/SimpleAttachmentTest.java | 137 ++++++++++ components/camel-csimple-joor/pom.xml | 5 + .../language/csimple/joor/JoorCSimpleCompiler.java | 30 +- .../camel-csimple-joor/src/test/data/123.txt | 1 + .../camel-csimple-joor/src/test/data/message1.xml | 24 ++ .../camel-csimple-joor/src/test/data/message2.xml | 24 ++ .../csimple/joor/CSimpleAttachmentsTest.java | 107 ++++++++ .../org/apache/camel/spi/BeanProxyFactory.java | 2 +- .../camel/spi/SimpleLanguageFunctionFactory.java | 55 ++++ .../modules/languages/pages/simple-language.adoc | 25 ++ .../language/csimple/CSimpleCodeGenerator.java | 19 +- .../language/csimple/CSimpleExpressionParser.java | 7 +- .../camel/language/csimple/CSimpleHelper.java | 4 + .../language/csimple/CSimplePredicateParser.java | 7 +- .../language/simple/SimpleExpressionBuilder.java | 8 +- .../language/simple/SimpleExpressionParser.java | 2 +- .../language/simple/SimplePredicateParser.java | 2 +- .../camel/language/simple/ast/BaseSimpleNode.java | 6 +- .../language/simple/ast/BinaryExpression.java | 10 +- .../language/simple/ast/BooleanExpression.java | 2 +- .../camel/language/simple/ast/CompositeNodes.java | 6 +- .../camel/language/simple/ast/DoubleQuoteEnd.java | 2 +- .../language/simple/ast/DoubleQuoteStart.java | 4 +- .../language/simple/ast/LiteralExpression.java | 2 +- .../language/simple/ast/LogicalExpression.java | 10 +- .../camel/language/simple/ast/NullExpression.java | 2 +- .../language/simple/ast/NumericExpression.java | 2 +- .../language/simple/ast/SimpleFunctionEnd.java | 2 +- .../simple/ast/SimpleFunctionExpression.java | 65 ++++- .../language/simple/ast/SimpleFunctionStart.java | 18 +- .../camel/language/simple/ast/SimpleNode.java | 3 +- .../camel/language/simple/ast/SingleQuoteEnd.java | 2 +- .../language/simple/ast/SingleQuoteStart.java | 4 +- .../camel/language/simple/ast/UnaryExpression.java | 8 +- .../apache/camel/language/simple/SimpleTest.java | 2 + .../camel/support/builder/ExpressionBuilder.java | 34 +++ 45 files changed, 1290 insertions(+), 59 deletions(-) diff --git a/components/camel-attachments/pom.xml b/components/camel-attachments/pom.xml index f6849893c7d..d9e98326aaa 100644 --- a/components/camel-attachments/pom.xml +++ b/components/camel-attachments/pom.xml @@ -44,6 +44,11 @@ <artifactId>camel-support</artifactId> <version>${project.version}</version> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-core-languages</artifactId> + <version>${project.version}</version> + </dependency> <!-- attachments api --> <dependency> @@ -58,6 +63,12 @@ <artifactId>camel-test-junit5</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-bean</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/components/camel-attachments/src/generated/resources/META-INF/services/org/apache/camel/simple-function-factory/camel-attachments b/components/camel-attachments/src/generated/resources/META-INF/services/org/apache/camel/simple-function-factory/camel-attachments new file mode 100644 index 00000000000..7e9b7cb00c9 --- /dev/null +++ b/components/camel-attachments/src/generated/resources/META-INF/services/org/apache/camel/simple-function-factory/camel-attachments @@ -0,0 +1,2 @@ +# Generated by camel build tools - do NOT edit this file! +class=org.apache.camel.attachment.SimpleAttachmentFunction diff --git a/components/camel-attachments/src/main/docs/attachments.adoc b/components/camel-attachments/src/main/docs/attachments.adoc index 1c552cf1124..5cd8c5cf159 100644 --- a/components/camel-attachments/src/main/docs/attachments.adoc +++ b/components/camel-attachments/src/main/docs/attachments.adoc @@ -27,5 +27,5 @@ And if you want to add an attachment, to a Camel `Message` you can do as shown: [source,java] ---- AttachmentMessage attMsg = exchange.getIn(AttachmentMessage.class); -attMsg.addAttachment("message1.xml", new DataHandler(new FileDataSource(new File("myMessage1.xml")))); +attMsg.addAttachment("message1.xml", new DataHandler(new CamelFileDataSource(new File("myMessage1.xml")))); ---- diff --git a/components/camel-attachments/src/main/java/org/apache/camel/attachment/AttachmentExpressionBuilder.java b/components/camel-attachments/src/main/java/org/apache/camel/attachment/AttachmentExpressionBuilder.java new file mode 100644 index 00000000000..d86f06e6664 --- /dev/null +++ b/components/camel-attachments/src/main/java/org/apache/camel/attachment/AttachmentExpressionBuilder.java @@ -0,0 +1,225 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.attachment; + +import org.apache.camel.CamelContext; +import org.apache.camel.CamelExecutionException; +import org.apache.camel.Exchange; +import org.apache.camel.Expression; +import org.apache.camel.NoTypeConversionAvailableException; +import org.apache.camel.RuntimeCamelException; +import org.apache.camel.language.simple.SimpleExpressionBuilder; +import org.apache.camel.support.ExpressionAdapter; +import org.apache.camel.support.ObjectHelper; + +import static org.apache.camel.support.builder.ExpressionBuilder.simpleExpression; + +public class AttachmentExpressionBuilder { + + public static Expression attachments() { + return new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + return am.getAttachments(); + } + return null; + } + }; + } + + public static Expression attachmentsSize() { + return new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + return am.getAttachments().size(); + } + return 0; + } + }; + } + + public static Expression attachmentContent(final String key, final String type) { + return new ExpressionAdapter() { + private Class<?> clazz; + + @Override + public Object evaluate(Exchange exchange) { + Object answer = null; + if (exchange.getMessage() instanceof AttachmentMessage am) { + var dh = am.getAttachment(key); + try { + answer = dh.getContent(); + } catch (Exception e) { + throw new RuntimeException(e); + } + if (answer != null && clazz != null) { + try { + answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(clazz, answer); + } catch (NoTypeConversionAvailableException e) { + throw CamelExecutionException.wrapCamelExecutionException(exchange, e); + } + } + } + return answer; + } + + @Override + public void init(CamelContext context) { + if (type != null) { + try { + clazz = context.getClassResolver().resolveMandatoryClass(type); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + }; + } + + public static Expression attachmentContentHeader(final String key, final String name, final String type) { + return new ExpressionAdapter() { + private Class<?> clazz; + + @Override + public Object evaluate(Exchange exchange) { + Object answer = null; + if (exchange.getMessage() instanceof AttachmentMessage am) { + var ao = am.getAttachmentObject(key); + if (ao != null) { + answer = ao.getHeader(name); + if (answer != null && clazz != null) { + try { + answer = exchange.getContext().getTypeConverter().mandatoryConvertTo(clazz, answer); + } catch (NoTypeConversionAvailableException e) { + throw CamelExecutionException.wrapCamelExecutionException(exchange, e); + } + } + } + } + return answer; + } + + @Override + public void init(CamelContext context) { + if (type != null) { + try { + clazz = context.getClassResolver().resolveMandatoryClass(type); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + } + }; + } + + public static Expression attachmentContentType(final String key) { + return new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + var dh = am.getAttachment(key); + if (dh != null) { + return dh.getContentType(); + } + } + return null; + } + }; + } + + /** + * Returns an expression for the attachment value of exchange with the given name + * + * @param attachmentName the name of the attachment the expression will return + * @return an expression object which will return the property value + */ + public static Expression attachmentExpression(final String attachmentName) { + return attachmentExpression(simpleExpression(attachmentName), false); + } + + /** + * Returns an expression for the attachment value of exchange with the given name + * + * @param attachmentName the name of the attachment the expression will return + * @param mandatory whether the attachment is mandatory and if not present an exception is thrown + * @return an expression object which will return the attachment value + */ + public static Expression attachmentExpression(final Expression attachmentName, final boolean mandatory) { + return new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + String key = attachmentName.evaluate(exchange, String.class); + Object answer = am.getAttachment(key); + if (mandatory && answer == null) { + throw RuntimeCamelException.wrapRuntimeCamelException(new NoSuchAttachmentException(exchange, key)); + } + return answer; + } + return null; + } + + @Override + public void init(CamelContext context) { + super.init(context); + attachmentName.init(context); + } + + @Override + public String toString() { + return "attachment(" + attachmentName + ")"; + } + }; + } + + /** + * Returns an expression for the attachment value of exchange with the given name invoking methods defined in a + * simple OGNL notation + * + * @param ognl methods to invoke on the attachment in a simple OGNL syntax + */ + public static Expression attachmentOgnlExpression(final String ognl) { + return new SimpleExpressionBuilder.KeyedOgnlExpressionAdapter( + ognl, "attachmentOgnl(" + ognl + ")", + (exchange, exp) -> { + if (exchange.getMessage() instanceof AttachmentMessage am) { + String text = exp.evaluate(exchange, String.class); + var dh = am.getAttachment(text); + if (dh == null && ObjectHelper.isNumber(text)) { + try { + // fallback to lookup by numeric index + int idx = Integer.parseInt(text); + if (idx < am.getAttachments().size()) { + var it = am.getAttachments().values().iterator(); + for (int i = 0; i < idx; i++) { + it.next(); + } + dh = it.next(); + } + } catch (NumberFormatException e) { + // ignore + } + } + return dh; + } + return null; + }); + } + +} diff --git a/components/camel-attachments/src/main/java/org/apache/camel/attachment/CSimpleAttachmentHelper.java b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CSimpleAttachmentHelper.java new file mode 100644 index 00000000000..1252657eadd --- /dev/null +++ b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CSimpleAttachmentHelper.java @@ -0,0 +1,95 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.attachment; + +import java.util.Map; + +import jakarta.activation.DataHandler; + +import org.apache.camel.Exchange; + +public class CSimpleAttachmentHelper { + + public static Map<String, DataHandler> attachments(Exchange exchange) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + return am.getAttachments(); + } + return null; + } + + public static int attachmentsSize(Exchange exchange) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + return am.getAttachments().size(); + } + return 0; + } + + public static Object attachmentContent(Exchange exchange, String key) throws Exception { + if (exchange.getMessage() instanceof AttachmentMessage am) { + var dh = am.getAttachments().get(key); + if (dh != null) { + return dh.getContent(); + } + } + return null; + } + + public static Object attachmentContentAsText(Exchange exchange, String key) throws Exception { + Object data = attachmentContent(exchange, key); + if (data != null) { + return exchange.getContext().getTypeConverter().convertTo(String.class, exchange, data); + } + return null; + } + + public static <T> T attachmentContentAs(Exchange exchange, String key, Class<T> type) throws Exception { + Object data = attachmentContent(exchange, key); + if (data != null) { + return exchange.getContext().getTypeConverter().convertTo(type, exchange, data); + } + return null; + } + + public static String attachmentContentType(Exchange exchange, String key) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + var dh = am.getAttachments().get(key); + if (dh != null) { + return dh.getContentType(); + } + } + return null; + } + + public static String attachmentHeader(Exchange exchange, String key, String name) { + if (exchange.getMessage() instanceof AttachmentMessage am) { + var ao = am.getAttachmentObjects().get(key); + if (ao != null) { + return ao.getHeader(name); + } + } + return null; + } + + public static <T> T attachmentHeaderAs(Exchange exchange, String key, String name, Class<T> type) { + String data = attachmentHeader(exchange, key, name); + if (data != null) { + return exchange.getContext().getTypeConverter().convertTo(type, exchange, data); + } + return null; + } + +} diff --git a/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java index 1f26d9d6db7..3c2f1676f9c 100644 --- a/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java +++ b/components/camel-attachments/src/main/java/org/apache/camel/attachment/CamelFileDataSource.java @@ -21,10 +21,17 @@ import java.io.File; import jakarta.activation.FileDataSource; import jakarta.activation.FileTypeMap; +/** + * A {@link FileDataSource} that uses the file name/extension to determine the content-type. + */ public class CamelFileDataSource extends FileDataSource { private final String fileName; private FileTypeMap typeMap; + public CamelFileDataSource(File file) { + this(file, file.getName()); + } + public CamelFileDataSource(File file, String fileName) { super(file); this.fileName = fileName; diff --git a/components/camel-attachments/src/main/java/org/apache/camel/attachment/NoSuchAttachmentException.java b/components/camel-attachments/src/main/java/org/apache/camel/attachment/NoSuchAttachmentException.java new file mode 100644 index 00000000000..d1452e0b962 --- /dev/null +++ b/components/camel-attachments/src/main/java/org/apache/camel/attachment/NoSuchAttachmentException.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.attachment; + +import org.apache.camel.CamelExchangeException; +import org.apache.camel.Exchange; + +/** + * An exception caused when a mandatory attachment is not available on a message {@link Exchange} + * + * @see org.apache.camel.support.ExchangeHelper#getMandatoryProperty(Exchange, String, Class) + */ +public class NoSuchAttachmentException extends CamelExchangeException { + + private final String attachmentName; + private final transient Class<?> type; + + public NoSuchAttachmentException(Exchange exchange, String attachmentName) { + this(exchange, attachmentName, null); + } + + public NoSuchAttachmentException(Exchange exchange, String attachmentName, Class<?> type) { + super("No '" + attachmentName + "' attachment available" + (type != null ? " of type: " + type.getName() : "") + + reason(exchange, attachmentName), exchange); + this.attachmentName = attachmentName; + this.type = type; + } + + public String getAttachmentName() { + return attachmentName; + } + + public Class<?> getType() { + return type; + } + + protected static String reason(Exchange exchange, String propertyName) { + Object value = exchange.getProperty(propertyName); + return valueDescription(value); + } + + static String valueDescription(Object value) { + if (value == null) { + return ""; + } + return " but has type: " + value.getClass().getCanonicalName(); + } +} diff --git a/components/camel-attachments/src/main/java/org/apache/camel/attachment/SimpleAttachmentFunction.java b/components/camel-attachments/src/main/java/org/apache/camel/attachment/SimpleAttachmentFunction.java new file mode 100644 index 00000000000..c0accf73c8a --- /dev/null +++ b/components/camel-attachments/src/main/java/org/apache/camel/attachment/SimpleAttachmentFunction.java @@ -0,0 +1,301 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.attachment; + +import org.apache.camel.CamelContext; +import org.apache.camel.Expression; +import org.apache.camel.language.simple.types.SimpleParserException; +import org.apache.camel.spi.SimpleLanguageFunctionFactory; +import org.apache.camel.spi.annotations.JdkService; +import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.OgnlHelper; +import org.apache.camel.util.StringHelper; +import org.apache.camel.util.StringQuoteHelper; + +@JdkService(SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments") +public class SimpleAttachmentFunction implements SimpleLanguageFunctionFactory { + + @Override + public Expression createFunction(CamelContext camelContext, String function, int index) { + if ("attachments".equals(function)) { + return AttachmentExpressionBuilder.attachments(); + } else if ("attachments.size".equals(function) || "attachments.size()".equals(function) + || "attachments.length".equals(function) || "attachments.length()".equals(function)) { + return AttachmentExpressionBuilder.attachmentsSize(); + } + + String remainder = ifStartsWithReturnRemainder("attachmentContent(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContent(key)} was: " + + function, + index); + } + return AttachmentExpressionBuilder.attachmentContent(values, null); + } + remainder = ifStartsWithReturnRemainder("attachmentContentAs(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentAs(key,type)} was: " + + function, + index); + } + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); + if (tokens.length > 2) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentAs(key,type)} was: " + function, index); + } + String key = tokens[0]; + String type = tokens[1]; + return AttachmentExpressionBuilder.attachmentContent(key, type); + } + remainder = ifStartsWithReturnRemainder("attachmentContentAsText(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentAsText(key)}} was: " + + function, + index); + } + return AttachmentExpressionBuilder.attachmentContent(values, "String"); + } + + remainder = ifStartsWithReturnRemainder("attachmentHeaderAs(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeaderAs(key,name,type)} was: " + function, index); + } + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); + if (tokens.length != 3) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeaderAs(key,name,type)} was: " + function, + index); + } + String key = tokens[0]; + String name = tokens[1]; + String type = tokens[2]; + return AttachmentExpressionBuilder.attachmentContentHeader(key, name, type); + } + remainder = ifStartsWithReturnRemainder("attachmentHeader(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeader(key,name)} was: " + + function, + index); + } + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); + if (tokens.length != 2) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeader(key,name)} was: " + function, + index); + } + String key = tokens[0]; + String name = tokens[1]; + return AttachmentExpressionBuilder.attachmentContentHeader(key, name, null); + } + + remainder = ifStartsWithReturnRemainder("attachmentContentType(", function); + if (remainder != null) { + String key = StringHelper.before(remainder, ")"); + if (key == null || ObjectHelper.isEmpty(key)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentType(key)} was: " + + function, + index); + } + return AttachmentExpressionBuilder.attachmentContentType(key); + } + + remainder = ifStartsWithReturnRemainder("attachment", function); + if (remainder != null) { + // remove leading character (dot, colon or ?) + if (remainder.startsWith(".") || remainder.startsWith(":") || remainder.startsWith("?")) { + remainder = remainder.substring(1); + } + // remove starting and ending brackets + if (remainder.startsWith("[") && remainder.endsWith("]")) { + remainder = remainder.substring(1, remainder.length() - 1); + } + + // validate syntax + boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder); + if (invalid) { + throw new SimpleParserException("Valid syntax: ${attachment.OGNL} was: " + function, index); + } + + if (OgnlHelper.isValidOgnlExpression(remainder)) { + // ognl based attachment + return AttachmentExpressionBuilder.attachmentOgnlExpression(remainder); + } else { + // regular attachment + return AttachmentExpressionBuilder.attachmentExpression(remainder); + } + } + return null; + } + + @Override + public String createCode(CamelContext camelContext, String function, int index) { + if ("attachments".equals(function)) { + return "attachments(exchange)"; + } else if ("attachments.size".equals(function) || "attachments.size()".equals(function) + || "attachments.length".equals(function) || "attachments.length()".equals(function)) { + return "attachmentsSize(exchange)"; + } + + String remainder = ifStartsWithReturnRemainder("attachmentContent(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContent(key)} was: " + + function, + index); + } + String key = StringHelper.removeQuotes(values); + key = key.trim(); + return "attachmentContent(exchange, \"" + key + "\")"; + } + remainder = ifStartsWithReturnRemainder("attachmentContentAs(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentAs(key,type)} was: " + + function, + index); + } + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); + if (tokens.length > 2) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentAs(key,type)} was: " + function, index); + } + String key = tokens[0]; + String type = tokens[1]; + key = StringHelper.removeQuotes(key); + key = key.trim(); + type = appendClass(type); + type = type.replace('$', '.'); + type = type.trim(); + return "attachmentContentAs(exchange, \"" + key + "\", " + type + ")"; + } + remainder = ifStartsWithReturnRemainder("attachmentContentAsText(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentAsText(key)}} was: " + + function, + index); + } + String key = StringHelper.removeQuotes(values); + key = key.trim(); + return "attachmentContentAsText(exchange, \"" + key + "\")"; + } + remainder = ifStartsWithReturnRemainder("attachmentContentType(", function); + if (remainder != null) { + String key = StringHelper.before(remainder, ")"); + if (key == null || ObjectHelper.isEmpty(key)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentContentType(key)} was: " + + function, + index); + } + key = StringHelper.removeQuotes(key); + key = key.trim(); + return "attachmentContentType(exchange, \"" + key + "\")"; + } + remainder = ifStartsWithReturnRemainder("attachmentHeader(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeader(key,name)} was: " + + function, + index); + } + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); + if (tokens.length != 2) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeader(key,name)} was: " + function, index); + } + String key = tokens[0]; + String name = tokens[1]; + key = StringHelper.removeQuotes(key); + key = key.trim(); + name = StringHelper.removeQuotes(name); + name = name.trim(); + return "attachmentHeader(exchange, \"" + key + "\", \"" + name + "\")"; + } + remainder = ifStartsWithReturnRemainder("attachmentHeaderAs(", function); + if (remainder != null) { + String values = StringHelper.before(remainder, ")"); + if (values == null || ObjectHelper.isEmpty(values)) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeaderAs(key,name,type)} was: " + + function, + index); + } + String[] tokens = StringQuoteHelper.splitSafeQuote(values, ',', false); + if (tokens.length != 3) { + throw new SimpleParserException( + "Valid syntax: ${attachmentHeaderAs(key,name,type)} was: " + function, index); + } + String key = tokens[0]; + String name = tokens[1]; + String type = tokens[2]; + key = StringHelper.removeQuotes(key); + key = key.trim(); + name = StringHelper.removeQuotes(name); + name = name.trim(); + type = appendClass(type); + type = type.replace('$', '.'); + type = type.trim(); + return "attachmentHeaderAs(exchange, \"" + key + "\", \"" + name + "\", " + type + ")"; + } + + return null; + } + + private String ifStartsWithReturnRemainder(String prefix, String text) { + if (text.startsWith(prefix)) { + String remainder = text.substring(prefix.length()); + if (!remainder.isEmpty()) { + return remainder; + } + } + return null; + } + + private static String appendClass(String type) { + type = StringHelper.removeQuotes(type); + if (!type.endsWith(".class")) { + type = type + ".class"; + } + return type; + } + +} diff --git a/components/camel-attachments/src/test/data/123.txt b/components/camel-attachments/src/test/data/123.txt new file mode 100644 index 00000000000..ee2b8364542 --- /dev/null +++ b/components/camel-attachments/src/test/data/123.txt @@ -0,0 +1 @@ +456 \ No newline at end of file diff --git a/components/camel-attachments/src/test/java/org/apache/camel/attachment/SimpleAttachmentTest.java b/components/camel-attachments/src/test/java/org/apache/camel/attachment/SimpleAttachmentTest.java new file mode 100644 index 00000000000..97221b87bd0 --- /dev/null +++ b/components/camel-attachments/src/test/java/org/apache/camel/attachment/SimpleAttachmentTest.java @@ -0,0 +1,137 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.attachment; + +import java.io.File; + +import jakarta.activation.DataHandler; + +import org.apache.camel.Exchange; +import org.apache.camel.test.junit5.LanguageTestSupport; +import org.junit.jupiter.api.Test; + +public class SimpleAttachmentTest extends LanguageTestSupport { + + @Override + protected String getLanguageName() { + return "simple"; + } + + @Override + protected void populateExchange(Exchange exchange) { + super.populateExchange(exchange); + + DefaultAttachment da + = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new File("src/test/data/message1.xml")))); + da.addHeader("orderId", "123"); + exchange.getIn(AttachmentMessage.class).addAttachmentObject("message1.xml", da); + + da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new File("src/test/data/message2.xml")))); + da.addHeader("orderId", "456"); + exchange.getIn(AttachmentMessage.class).addAttachmentObject("message2.xml", da); + + da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new File("src/test/data/123.txt")))); + da.addHeader("orderId", "0"); + exchange.getIn(AttachmentMessage.class).addAttachmentObject("123.txt", da); + } + + @Test + public void testAttachments() throws Exception { + var map = exchange.getMessage(AttachmentMessage.class).getAttachments(); + assertExpression("${attachments}", map); + assertExpression("${attachments.size}", 3); + assertExpression("${attachments.length}", 3); + + exchange.getMessage(AttachmentMessage.class).removeAttachment("message1.xml"); + assertExpression("${attachments.size}", 2); + assertExpression("${attachments.length}", 2); + + exchange.getMessage(AttachmentMessage.class).removeAttachment("message2.xml"); + assertExpression("${attachments.size}", 1); + assertExpression("${attachments.length}", 1); + + exchange.getMessage(AttachmentMessage.class).removeAttachment("123.txt"); + assertExpression("${attachments.size}", 0); + assertExpression("${attachments.length}", 0); + } + + @Test + public void testAttachmentContent() throws Exception { + var map = exchange.getMessage(AttachmentMessage.class).getAttachments(); + Object is1 = map.get("message1.xml").getContent(); + String xml1 = context.getTypeConverter().convertTo(String.class, is1); + Object is2 = map.get("message2.xml").getContent(); + String xml2 = context.getTypeConverter().convertTo(String.class, is2); + + assertExpression("${attachmentContent(message1.xml)}", xml1); + assertExpression("${attachmentContentAs(message2.xml,String)}", xml2); + assertExpression("${attachmentContentAsText(message2.xml)}", xml2); + assertExpression("${attachmentContent(123.txt)}", "456"); + assertExpression("${attachmentContentAs(123.txt,String)}", "456"); + assertExpression("${attachmentContentAsText(123.txt)}", "456"); + } + + @Test + public void testAttachmentContentHeader() throws Exception { + assertExpression("${attachmentHeader(message1.xml,orderId)}", "123"); + assertExpression("${attachmentHeader(message1.xml,foo)}", null); + assertExpression("${attachmentHeaderAs(message2.xml,orderId,int)}", 456); + assertExpression("${attachmentHeader(123.txt,orderId)}", 0); + assertExpression("${attachmentHeader(123.txt,unknown)}", null); + } + + @Test + public void testAttachmentContentType() throws Exception { + assertExpression("${attachmentContentType(message1.xml)}", "application/octet-stream"); + assertExpression("${attachmentContentType(message2.xml)}", "application/octet-stream"); + assertExpression("${attachmentContentType(123.txt)}", "application/octet-stream"); + } + + @Test + public void testAttachmentOgnlByName() throws Exception { + var map = exchange.getMessage(AttachmentMessage.class).getAttachments(); + Object is1 = map.get("message1.xml").getContent(); + String xml1 = context.getTypeConverter().convertTo(String.class, is1); + Object is2 = map.get("message2.xml").getContent(); + String xml2 = context.getTypeConverter().convertTo(String.class, is2); + + assertExpression("${attachment[message1.xml].name}", "message1.xml"); + assertExpression("${attachment[message1.xml].contentType}", "application/octet-stream"); + assertExpression("${attachment[message1.xml].content}", xml1); + + assertExpression("${attachment[message2.xml].name}", "message2.xml"); + assertExpression("${attachment[message2.xml].contentType}", "application/octet-stream"); + assertExpression("${attachment[message2.xml].content}", xml2); + + assertExpression("${attachment[123.txt].name}", "123.txt"); + assertExpression("${attachment[123.txt].contentType}", "application/octet-stream"); + assertExpression("${attachment[123.txt].content}", "456"); + } + + @Test + public void testAttachmentOgnlByIndex() throws Exception { + assertExpression("${attachment[0].name}", "message1.xml"); + assertExpression("${attachment[0].contentType}", "application/octet-stream"); + + assertExpression("${attachment[1].name}", "message2.xml"); + assertExpression("${attachment[1].contentType}", "application/octet-stream"); + + assertExpression("${attachment[2].name}", "123.txt"); + assertExpression("${attachment[2].contentType}", "application/octet-stream"); + assertExpression("${attachment[2].content}", "456"); + } +} diff --git a/components/camel-csimple-joor/pom.xml b/components/camel-csimple-joor/pom.xml index 5ee38dd7216..79a9946b6f2 100644 --- a/components/camel-csimple-joor/pom.xml +++ b/components/camel-csimple-joor/pom.xml @@ -62,6 +62,11 @@ <type>test-jar</type> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-attachments</artifactId> + <scope>test</scope> + </dependency> <dependency> <groupId>org.hamcrest</groupId> <artifactId>hamcrest</artifactId> diff --git a/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java b/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java index 4ef12b57527..1ba78af5813 100644 --- a/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java +++ b/components/camel-csimple-joor/src/main/java/org/apache/camel/language/csimple/joor/JoorCSimpleCompiler.java @@ -17,17 +17,21 @@ package org.apache.camel.language.csimple.joor; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; import org.apache.camel.StaticService; import org.apache.camel.language.csimple.CSimpleCodeGenerator; import org.apache.camel.language.csimple.CSimpleCompiler; import org.apache.camel.language.csimple.CSimpleExpression; import org.apache.camel.language.csimple.CSimpleGeneratedCode; +import org.apache.camel.spi.SimpleLanguageFunctionFactory; import org.apache.camel.spi.annotations.JdkService; +import org.apache.camel.support.ResolverHelper; import org.apache.camel.support.ScriptHelper; import org.apache.camel.support.service.ServiceSupport; import org.apache.camel.util.StopWatch; @@ -40,15 +44,26 @@ import org.slf4j.LoggerFactory; * jOOR compiler for csimple language. */ @JdkService(CSimpleCompiler.FACTORY) -public class JoorCSimpleCompiler extends ServiceSupport implements CSimpleCompiler, StaticService { +public class JoorCSimpleCompiler extends ServiceSupport implements CSimpleCompiler, CamelContextAware, StaticService { private static final Logger LOG = LoggerFactory.getLogger(JoorCSimpleCompiler.class); private static final AtomicInteger UUID = new AtomicInteger(); + private CamelContext camelContext; private Set<String> imports = new TreeSet<>(); private Map<String, String> aliases; private int counter; private long taken; + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + public Set<String> getImports() { return imports; } @@ -127,6 +142,7 @@ public class JoorCSimpleCompiler extends ServiceSupport implements CSimpleCompil script = script.trim(); CSimpleCodeGenerator generator = new CSimpleCodeGenerator(); + generator.setCamelContext(camelContext); if (aliases != null && !aliases.isEmpty()) { generator.setAliases(aliases); } @@ -150,7 +166,17 @@ public class JoorCSimpleCompiler extends ServiceSupport implements CSimpleCompil @Override protected void doStart() throws Exception { - // noop + // check if camel-attachment is on classpath which then includes custom csimple functions + if (camelContext != null) { + Optional<SimpleLanguageFunctionFactory> factory = ResolverHelper.resolveService( + camelContext, + camelContext.getCamelContextExtension().getBootstrapFactoryFinder(), + SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments", + SimpleLanguageFunctionFactory.class); + if (factory.isPresent()) { + addImport("import static org.apache.camel.attachment.CSimpleAttachmentHelper.*"); + } + } } @Override diff --git a/components/camel-csimple-joor/src/test/data/123.txt b/components/camel-csimple-joor/src/test/data/123.txt new file mode 100644 index 00000000000..ee2b8364542 --- /dev/null +++ b/components/camel-csimple-joor/src/test/data/123.txt @@ -0,0 +1 @@ +456 \ No newline at end of file diff --git a/components/camel-csimple-joor/src/test/data/message1.xml b/components/camel-csimple-joor/src/test/data/message1.xml new file mode 100644 index 00000000000..1a85d067b42 --- /dev/null +++ b/components/camel-csimple-joor/src/test/data/message1.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<person user="james"> + <firstName>James</firstName> + <lastName>Strachan</lastName> + <city>London</city> +</person> \ No newline at end of file diff --git a/components/camel-csimple-joor/src/test/data/message2.xml b/components/camel-csimple-joor/src/test/data/message2.xml new file mode 100644 index 00000000000..73c88bfa2e5 --- /dev/null +++ b/components/camel-csimple-joor/src/test/data/message2.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--> +<person user="hiram"> + <firstName>Hiram</firstName> + <lastName>Chirino</lastName> + <city>Tampa</city> +</person> \ No newline at end of file diff --git a/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/CSimpleAttachmentsTest.java b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/CSimpleAttachmentsTest.java new file mode 100644 index 00000000000..ded77bf8737 --- /dev/null +++ b/components/camel-csimple-joor/src/test/java/org/apache/camel/language/csimple/joor/CSimpleAttachmentsTest.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.language.csimple.joor; + +import java.io.File; + +import jakarta.activation.DataHandler; + +import org.apache.camel.Exchange; +import org.apache.camel.attachment.AttachmentMessage; +import org.apache.camel.attachment.CamelFileDataSource; +import org.apache.camel.attachment.DefaultAttachment; +import org.apache.camel.test.junit5.LanguageTestSupport; +import org.junit.jupiter.api.Test; + +public class CSimpleAttachmentsTest extends LanguageTestSupport { + + @Override + protected String getLanguageName() { + return "csimple"; + } + + @Override + protected void populateExchange(Exchange exchange) { + super.populateExchange(exchange); + + DefaultAttachment da + = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new File("src/test/data/message1.xml")))); + da.addHeader("orderId", "123"); + exchange.getIn(AttachmentMessage.class).addAttachmentObject("message1.xml", da); + + da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new File("src/test/data/message2.xml")))); + da.addHeader("orderId", "456"); + exchange.getIn(AttachmentMessage.class).addAttachmentObject("message2.xml", da); + + da = new DefaultAttachment(new DataHandler(new CamelFileDataSource(new File("src/test/data/123.txt")))); + da.addHeader("orderId", "0"); + exchange.getIn(AttachmentMessage.class).addAttachmentObject("123.txt", da); + } + + @Test + public void testAttachments() throws Exception { + var map = exchange.getMessage(AttachmentMessage.class).getAttachments(); + assertExpression("${attachments}", map); + assertExpression("${attachments.size}", 3); + assertExpression("${attachments.length}", 3); + + exchange.getMessage(AttachmentMessage.class).removeAttachment("message1.xml"); + assertExpression("${attachments.size}", 2); + assertExpression("${attachments.length}", 2); + + exchange.getMessage(AttachmentMessage.class).removeAttachment("message2.xml"); + assertExpression("${attachments.size}", 1); + assertExpression("${attachments.length}", 1); + + exchange.getMessage(AttachmentMessage.class).removeAttachment("123.txt"); + assertExpression("${attachments.size}", 0); + assertExpression("${attachments.length}", 0); + } + + @Test + public void testAttachmentContent() throws Exception { + var map = exchange.getMessage(AttachmentMessage.class).getAttachments(); + Object is1 = map.get("message1.xml").getContent(); + String xml1 = context.getTypeConverter().convertTo(String.class, is1); + Object is2 = map.get("message2.xml").getContent(); + String xml2 = context.getTypeConverter().convertTo(String.class, is2); + + assertExpression("${attachmentContent(message1.xml)}", xml1); + assertExpression("${attachmentContentAs(message2.xml,String)}", xml2); + assertExpression("${attachmentContentAsText(message2.xml)}", xml2); + assertExpression("${attachmentContent(123.txt)}", "456"); + assertExpression("${attachmentContentAs(123.txt,String)}", "456"); + assertExpression("${attachmentContentAsText(123.txt)}", "456"); + } + + @Test + public void testAttachmentContentHeader() throws Exception { + assertExpression("${attachmentHeader(message1.xml,orderId)}", "123"); + assertExpression("${attachmentHeader(message1.xml,foo)}", null); + assertExpression("${attachmentHeaderAs(message2.xml,orderId,int)}", 456); + assertExpression("${attachmentHeader(123.txt,orderId)}", 0); + assertExpression("${attachmentHeader(123.txt,unknown)}", null); + } + + @Test + public void testAttachmentContentType() throws Exception { + assertExpression("${attachmentContentType(message1.xml)}", "application/octet-stream"); + assertExpression("${attachmentContentType(message2.xml)}", "application/octet-stream"); + assertExpression("${attachmentContentType(123.txt)}", "text/plain"); + } + +} diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java b/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java index 424ba9c9955..0ac6bd76cec 100644 --- a/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java +++ b/core/camel-api/src/main/java/org/apache/camel/spi/BeanProxyFactory.java @@ -31,7 +31,7 @@ public interface BeanProxyFactory { String FACTORY = "bean-proxy-factory"; /** - * Creates a proxy bean facaded with the interfaces that when invoked will send the data as a message to a Camel + * Creates a proxy bean facade with the interfaces that when invoked will send the data as a message to a Camel * endpoint. * * @param endpoint the endpoint to send to when the proxy is invoked diff --git a/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java new file mode 100644 index 00000000000..3eb0b27c9ce --- /dev/null +++ b/core/camel-api/src/main/java/org/apache/camel/spi/SimpleLanguageFunctionFactory.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.spi; + +import org.apache.camel.CamelContext; +import org.apache.camel.Expression; + +/** + * A factory for extending the simple language with functions from external components. + * <p/> + * This requires to have the external component JAR on the classpath. + */ +public interface SimpleLanguageFunctionFactory { + + /** + * Service factory key. + */ + String FACTORY = "simple-function-factory"; + + /** + * Creates the {@link Expression} that performs the function. + * + * @param camelContext the camel context + * @param function the function + * @param index index of the function in the literal input + * + * @return the created function as an expression, or <tt>null</tt> if not supported by this factory. + */ + Expression createFunction(CamelContext camelContext, String function, int index); + + /** + * Creates the Java source code that performs the function (for csimple). + * + * @param camelContext the camel context + * @param function the function + * @param index index of the function in the literal input + * @return the source code or <tt>null</tt> if not supported by this factory. + */ + String createCode(CamelContext camelContext, String function, int index); + +} 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 e90f73efa7c..3d9afef38d0 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 @@ -128,6 +128,7 @@ Camel OGNL expression. classname |headers |Map |refer to the headers +|headers.size |int |The number of headers |variable.foo |Object |refer to the foo variable @@ -140,6 +141,7 @@ value using a Camel OGNL expression. classname |variables |Map |refer to the variables +|variables.size |int |The number of variables |exchangeProperty.foo |Object |refer to the foo property on the exchange @@ -321,6 +323,29 @@ true, otherwise the value of `falseExp` is returned. This function is similar to |======================================================================= +== Attachment functions + +From *Camel 4.10* onwards then Camel has built-in attachment functions making it easy to obtain +details from attachments stored on the Camel Message such as from HTTP file uploads, email with file attachments etc. + +This requires having `camel-attachments` JAR on the classpath. + +[width="100%",cols="10%,10%,80%",options="header",] +|======================================================================= +|Function |Type |Description + +|attachments |Map | All the attachments as a `Map<String,DataHandler>`. +|attachments.size | int | The number of attachments. Is 0 if there are no attachments. +|attachmentContentAsText(key) | String | The content of the attachment as text (ie `String`). +|attachmentContent(key) | Object | The content of the attachment. +|attachmentContentAs(key,_type_) | Object | The content of the attachment, converted to the given type. +|attachmentHeader(key,name) | Object | The attachment header with the given name. +|attachmentHeaderAs(key,name,_type_) | Object | The attachment header with the given name, converted to the given type. +|attachment[key] | DataHandler | The `DataHandler` for the given attachment. +|attachment.*OGNL* | Object | refer to the foo attachment on the exchange and invoke its value using a Camel OGNL expression. +|======================================================================= + + == OGNL expression support The xref:simple-language.adoc[Simple] and xref:simple-language.adoc[Bean] languages support a Camel xref:ognl-language.adoc[OGNL] notation for invoking beans in a chain like fashion. diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java index eaf25ee5fb7..f53be1bed93 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleCodeGenerator.java @@ -22,6 +22,8 @@ import java.util.Set; import java.util.TreeSet; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.camel.CamelContext; +import org.apache.camel.CamelContextAware; import org.apache.camel.util.StringHelper; /** @@ -29,13 +31,24 @@ import org.apache.camel.util.StringHelper; * * @see CSimpleGeneratedCode */ -public class CSimpleCodeGenerator { +public class CSimpleCodeGenerator implements CamelContextAware { private static final AtomicInteger UUID = new AtomicInteger(); + private CamelContext camelContext; private Set<String> imports = new TreeSet<>(); private Map<String, String> aliases = new HashMap<>(); + @Override + public CamelContext getCamelContext() { + return camelContext; + } + + @Override + public void setCamelContext(CamelContext camelContext) { + this.camelContext = camelContext; + } + public Set<String> getImports() { return imports; } @@ -127,14 +140,14 @@ public class CSimpleCodeGenerator { if (predicate) { CSimplePredicateParser parser = new CSimplePredicateParser(); - script = parser.parsePredicate(script); + script = parser.parsePredicate(camelContext, script); if (script.isBlank()) { // a predicate that is whitespace is regarded as false script = "false"; } } else { CSimpleExpressionParser parser = new CSimpleExpressionParser(); - script = parser.parseExpression(script); + script = parser.parseExpression(camelContext, script); if (script.isBlank()) { // an expression can be whitespace, but then we need to wrap this in quotes script = "\"" + script + "\""; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java index 0922212d514..bc06af83f13 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimpleExpressionParser.java @@ -16,6 +16,7 @@ */ package org.apache.camel.language.csimple; +import org.apache.camel.CamelContext; import org.apache.camel.language.simple.SimpleExpressionParser; /** @@ -24,8 +25,12 @@ import org.apache.camel.language.simple.SimpleExpressionParser; public class CSimpleExpressionParser { public String parseExpression(String expression) { + return parseExpression(null, expression); + } + + public String parseExpression(CamelContext camelContext, String expression) { // reuse simple language parser but output the result as java code - SimpleExpressionParser parser = new SimpleExpressionParser(null, expression, true, null); + SimpleExpressionParser parser = new SimpleExpressionParser(camelContext, expression, true, null); return parser.parseCode(); } 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 e62891dba99..428f79be280 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 @@ -205,6 +205,10 @@ public final class CSimpleHelper { return exchange.getVariables(); } + public static int variablesSize(Exchange exchange) { + return exchange.getVariables().size(); + } + public static String bodyOneLine(Exchange exchange) { String body = exchange.getIn().getBody(String.class); if (body == null) { diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java index 3ba6f1a5a02..b2ce514f111 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/csimple/CSimplePredicateParser.java @@ -16,6 +16,7 @@ */ package org.apache.camel.language.csimple; +import org.apache.camel.CamelContext; import org.apache.camel.language.simple.SimplePredicateParser; /** @@ -24,8 +25,12 @@ import org.apache.camel.language.simple.SimplePredicateParser; public class CSimplePredicateParser { public String parsePredicate(String predicate) { + return parsePredicate(null, predicate); + } + + public String parsePredicate(CamelContext camelContext, String predicate) { // reuse simple language parser but output the result as java code - SimplePredicateParser parser = new SimplePredicateParser(null, predicate, true, null); + SimplePredicateParser parser = new SimplePredicateParser(camelContext, predicate, true, null); return parser.parseCode(); } 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 dab6fdb1ec4..e616167f8d1 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 @@ -1125,9 +1125,7 @@ public final class SimpleExpressionBuilder { public String toString() { return "type:" + name; } - } - - ; + }; } /** @@ -1197,8 +1195,8 @@ public final class SimpleExpressionBuilder { private Expression ognlExpression; private Language beanLanguage; - KeyedOgnlExpressionAdapter(String ognl, String toStringValue, - KeyedEntityRetrievalStrategy keyedEntityRetrievalStrategy) { + public KeyedOgnlExpressionAdapter(String ognl, String toStringValue, + KeyedEntityRetrievalStrategy keyedEntityRetrievalStrategy) { this.ognl = ognl; this.toStringValue = toStringValue; this.keyedEntityRetrievalStrategy = keyedEntityRetrievalStrategy; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java index 56260a8b7b7..1ee19757064 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimpleExpressionParser.java @@ -206,7 +206,7 @@ public class SimpleExpressionParser extends BaseSimpleParser { StringBuilder sb = new StringBuilder(256); boolean firstIsLiteral = false; for (SimpleNode node : nodes) { - String exp = node.createCode(expression); + String exp = node.createCode(camelContext, expression); if (exp != null) { if (sb.isEmpty() && node instanceof LiteralNode) { firstIsLiteral = true; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java index 66eef5fd785..32386e86852 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/SimplePredicateParser.java @@ -165,7 +165,7 @@ public class SimplePredicateParser extends BaseSimpleParser { protected String doParseCode() { StringBuilder sb = new StringBuilder(256); for (SimpleNode node : nodes) { - String exp = node.createCode(expression); + String exp = node.createCode(camelContext, expression); SimpleExpressionParser.parseLiteralNode(sb, node, exp); } String code = sb.toString(); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java index 374212071f3..ff77e75cb40 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BaseSimpleNode.java @@ -16,6 +16,7 @@ */ package org.apache.camel.language.simple.ast; +import org.apache.camel.CamelContext; import org.apache.camel.language.simple.types.SimpleParserException; import org.apache.camel.language.simple.types.SimpleToken; @@ -40,10 +41,11 @@ public abstract class BaseSimpleNode implements SimpleNode { return token.getText(); } - protected static String createCode(String expression, CompositeNodes block) throws SimpleParserException { + protected static String createCode(CamelContext camelContext, String expression, CompositeNodes block) + throws SimpleParserException { String answer = null; if (block != null) { - answer = block.createCode(expression); + answer = block.createCode(camelContext, expression); } // use double quote as this become used as string literals in the generated code if (answer == null) { diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java index e6349c533d7..64fec4a9d5b 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BinaryExpression.java @@ -282,16 +282,16 @@ public class BinaryExpression extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { - return BaseSimpleParser.CODE_START + doCreateCode(expression) + BaseSimpleParser.CODE_END; + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleParser.CODE_START + doCreateCode(camelContext, expression) + BaseSimpleParser.CODE_END; } - private String doCreateCode(String expression) throws SimpleParserException { + private String doCreateCode(CamelContext camelContext, String expression) throws SimpleParserException { org.apache.camel.util.ObjectHelper.notNull(left, "left node", this); org.apache.camel.util.ObjectHelper.notNull(right, "right node", this); - final String leftExp = left.createCode(expression); - final String rightExp = right.createCode(expression); + final String leftExp = left.createCode(camelContext, expression); + final String rightExp = right.createCode(camelContext, expression); if (operator == BinaryOperatorType.EQ) { return "isEqualTo(exchange, " + leftExp + ", " + rightExp + ")"; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java index eec56f05212..ea26653b471 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/BooleanExpression.java @@ -53,7 +53,7 @@ public class BooleanExpression extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { return value ? "true" : "false"; } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java index 3182e8b56fb..b7cf251c8dd 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/CompositeNodes.java @@ -69,15 +69,15 @@ public class CompositeNodes extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { if (children.isEmpty()) { return null; } else if (children.size() == 1) { - return children.get(0).createCode(expression); + return children.get(0).createCode(camelContext, expression); } else { StringBuilder sb = new StringBuilder(256); for (SimpleNode child : children) { - String code = child.createCode(expression); + String code = child.createCode(camelContext, expression); if (code != null) { sb.append(code); } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java index 721652022d0..62e0e802b0e 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteEnd.java @@ -36,7 +36,7 @@ public class DoubleQuoteEnd extends BaseSimpleNode implements BlockEnd { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { return null; } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java index bd4428db5b6..1a35af02c39 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/DoubleQuoteStart.java @@ -60,7 +60,7 @@ public class DoubleQuoteStart extends BaseSimpleNode implements BlockStart { } @Override - public String createCode(String expression) throws SimpleParserException { - return BaseSimpleNode.createCode(expression, block); + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleNode.createCode(camelContext, expression, block); } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java index 60167e82152..5c59c101e1a 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LiteralExpression.java @@ -62,7 +62,7 @@ public class LiteralExpression extends BaseSimpleNode implements LiteralNode { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { return getText(); } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java index 9fc06ecd133..afbcd2efec0 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/LogicalExpression.java @@ -123,16 +123,16 @@ public class LogicalExpression extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { - return BaseSimpleParser.CODE_START + doCreateCode(expression) + BaseSimpleParser.CODE_END; + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleParser.CODE_START + doCreateCode(camelContext, expression) + BaseSimpleParser.CODE_END; } - private String doCreateCode(String expression) throws SimpleParserException { + private String doCreateCode(CamelContext camelContext, String expression) throws SimpleParserException { ObjectHelper.notNull(left, "left node", this); ObjectHelper.notNull(right, "right node", this); - final String leftExp = left.createCode(expression); - final String rightExp = right.createCode(expression); + final String leftExp = left.createCode(camelContext, expression); + final String rightExp = right.createCode(camelContext, expression); if (operator == LogicalOperatorType.AND) { return leftExp + " && " + rightExp; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java index 46fb25dec4b..00faa5cff1a 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NullExpression.java @@ -49,7 +49,7 @@ public class NullExpression extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { return "null"; } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java index ceda57ef05b..9f39a63e4f4 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/NumericExpression.java @@ -72,7 +72,7 @@ public class NumericExpression extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { // Double, Long or Integer if (number instanceof Double) { return number + "d"; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java index 5dac2d21c4e..bde21cff2a9 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionEnd.java @@ -36,7 +36,7 @@ public class SimpleFunctionEnd extends BaseSimpleNode implements BlockEnd { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { return 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 3c2a07311c9..55c6701ce6d 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 @@ -20,6 +20,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import org.apache.camel.CamelContext; import org.apache.camel.Expression; @@ -29,6 +30,8 @@ import org.apache.camel.language.simple.SimpleExpressionBuilder; import org.apache.camel.language.simple.types.SimpleParserException; import org.apache.camel.language.simple.types.SimpleToken; import org.apache.camel.spi.Language; +import org.apache.camel.spi.SimpleLanguageFunctionFactory; +import org.apache.camel.support.ResolverHelper; import org.apache.camel.support.builder.ExpressionBuilder; import org.apache.camel.util.ObjectHelper; import org.apache.camel.util.OgnlHelper; @@ -325,6 +328,14 @@ public class SimpleFunctionExpression extends LiteralExpression { return misc; } + // attachments + if ("attachments".equals(function) || ifStartsWithReturnRemainder("attachment", function) != null) { + Expression exp = createSimpleAttachments(camelContext, function); + if (exp != null) { + return exp; + } + } + if (strict) { throw new SimpleParserException("Unknown function: " + function, token.getIndex()); } else { @@ -332,6 +343,20 @@ public class SimpleFunctionExpression extends LiteralExpression { } } + private Expression createSimpleAttachments(CamelContext camelContext, String function) { + Optional<SimpleLanguageFunctionFactory> factory = ResolverHelper.resolveService( + camelContext, + camelContext.getCamelContextExtension().getBootstrapFactoryFinder(), + SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments", + SimpleLanguageFunctionFactory.class); + + if (factory.isEmpty()) { + throw new IllegalArgumentException( + "Cannot find SimpleLanguageFunctionFactory on classpath. Add camel-attachments to classpath."); + } + return factory.get().createFunction(camelContext, function, token.getIndex()); + } + private Expression createSimpleExpressionMessage(CamelContext camelContext, String function, boolean strict) { // messageAs String remainder = ifStartsWithReturnRemainder("messageAs(", function); @@ -435,6 +460,9 @@ public class SimpleFunctionExpression extends LiteralExpression { // headers function if ("in.headers".equals(function) || "headers".equals(function)) { return ExpressionBuilder.headersExpression(); + } else if ("headers.size".equals(function) || "headers.size()".equals(function) + || "headers.length".equals(function) || "headers.length()".equals(function)) { + return ExpressionBuilder.headersSizeExpression(); } // in header function @@ -492,6 +520,9 @@ public class SimpleFunctionExpression extends LiteralExpression { // variables function if ("variables".equals(function)) { return ExpressionBuilder.variablesExpression(); + } else if ("variables.size".equals(function) || "variables.size()".equals(function) + || "variables.length".equals(function) || "variables.length()".equals(function)) { + return ExpressionBuilder.variablesSizeExpression(); } // variable function @@ -874,11 +905,11 @@ public class SimpleFunctionExpression extends LiteralExpression { } @Override - public String createCode(String expression) throws SimpleParserException { - return BaseSimpleParser.CODE_START + doCreateCode(expression) + BaseSimpleParser.CODE_END; + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleParser.CODE_START + doCreateCode(camelContext, expression) + BaseSimpleParser.CODE_END; } - private String doCreateCode(String expression) throws SimpleParserException { + private String doCreateCode(CamelContext camelContext, String expression) throws SimpleParserException { String function = getText(); // return the function directly if we can create function without analyzing the prefix @@ -1090,6 +1121,14 @@ public class SimpleFunctionExpression extends LiteralExpression { return misc; } + // attachments + if ("attachments".equals(function) || ifStartsWithReturnRemainder("attachment", function) != null) { + String code = createCodeAttachments(camelContext, function); + if (code != null) { + return code; + } + } + throw new SimpleParserException("Unknown function: " + function, token.getIndex()); } @@ -1373,6 +1412,9 @@ public class SimpleFunctionExpression extends LiteralExpression { // headers function if ("in.headers".equals(function) || "headers".equals(function)) { return "message.getHeaders()"; + } else if ("headers.size".equals(function) || "headers.size()".equals(function) + || "headers.length".equals(function) || "headers.length()".equals(function)) { + return "message.getHeaders().size()"; } // in header function @@ -1485,6 +1527,9 @@ public class SimpleFunctionExpression extends LiteralExpression { // variables function if ("variables".equals(function)) { return "variables(exchange)"; + } else if ("variables.size".equals(function) || "variables.size()".equals(function) + || "variables.length".equals(function) || "variables.length()".equals(function)) { + return "variablesSize(exchange)"; } // variable @@ -1687,6 +1732,20 @@ public class SimpleFunctionExpression extends LiteralExpression { throw new SimpleParserException("Unknown file language syntax: " + remainder, token.getIndex()); } + private String createCodeAttachments(CamelContext camelContext, String function) { + Optional<SimpleLanguageFunctionFactory> factory = ResolverHelper.resolveService( + camelContext, + camelContext.getCamelContextExtension().getBootstrapFactoryFinder(), + SimpleLanguageFunctionFactory.FACTORY + "/camel-attachments", + SimpleLanguageFunctionFactory.class); + + if (factory.isEmpty()) { + throw new IllegalArgumentException( + "Cannot find SimpleLanguageFunctionFactory on classpath. Add camel-attachments to classpath."); + } + return factory.get().createCode(camelContext, function, token.getIndex()); + } + private String createCodeExpressionMisc(String function) { String remainder; diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java index 68e8950a730..852a6d64db2 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleFunctionStart.java @@ -149,25 +149,25 @@ public class SimpleFunctionStart extends BaseSimpleNode implements BlockStart { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { String answer; // a function can either be a simple literal function or contain nested functions if (block.getChildren().size() == 1 && block.getChildren().get(0) instanceof LiteralNode) { - answer = doCreateLiteralCode(expression); + answer = doCreateLiteralCode(camelContext, expression); } else { - answer = doCreateCompositeCode(expression); + answer = doCreateCompositeCode(camelContext, expression); } return answer; } - private String doCreateLiteralCode(String expression) { + private String doCreateLiteralCode(CamelContext camelContext, String expression) { SimpleFunctionExpression function = new SimpleFunctionExpression(this.getToken(), cacheExpression); LiteralNode literal = (LiteralNode) block.getChildren().get(0); function.addText(literal.getText()); - return function.createCode(expression); + return function.createCode(camelContext, expression); } - private String doCreateCompositeCode(String expression) { + private String doCreateCompositeCode(CamelContext camelContext, String expression) { StringBuilder sb = new StringBuilder(256); boolean quoteEmbeddedFunctions = false; @@ -181,7 +181,7 @@ public class SimpleFunctionStart extends BaseSimpleNode implements BlockStart { } else if (child instanceof SingleQuoteStart || child instanceof DoubleQuoteStart) { try { // pass in null when we evaluate the nested expressions - String text = child.createCode(null); + String text = child.createCode(camelContext, null); if (text != null) { if (quoteEmbeddedFunctions && !StringHelper.isQuoted(text)) { sb.append("'").append(text).append("'"); @@ -195,7 +195,7 @@ public class SimpleFunctionStart extends BaseSimpleNode implements BlockStart { } } else if (child instanceof SimpleFunctionStart) { // inlined function - String inlined = child.createCode(expression); + String inlined = child.createCode(camelContext, expression); sb.append(inlined); } } @@ -206,7 +206,7 @@ public class SimpleFunctionStart extends BaseSimpleNode implements BlockStart { SimpleFunctionExpression function = new SimpleFunctionExpression(token, cacheExpression); function.addText(exp); try { - return function.createCode(exp); + return function.createCode(camelContext, exp); } catch (SimpleParserException e) { // must rethrow parser exception as illegal syntax with details about the location throw new SimpleIllegalSyntaxException(expression, e.getIndex(), e.getMessage(), e); diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java index ed79da3df38..559af1791b6 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SimpleNode.java @@ -46,10 +46,11 @@ public interface SimpleNode { /** * Creates Java code based on this model. * + * @param camelContext the camel context * @param expression the input string * @return the created Java code * @throws org.apache.camel.language.simple.types.SimpleParserException should be thrown if error parsing the model */ - String createCode(String expression) throws SimpleParserException; + String createCode(CamelContext camelContext, String expression) throws SimpleParserException; } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java index 3e5b97f6734..081edaa09ee 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteEnd.java @@ -36,7 +36,7 @@ public class SingleQuoteEnd extends BaseSimpleNode implements BlockEnd { } @Override - public String createCode(String expression) throws SimpleParserException { + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { return null; } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java index ce23e4246d6..a7a907ec07f 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/SingleQuoteStart.java @@ -64,7 +64,7 @@ public class SingleQuoteStart extends BaseSimpleNode implements BlockStart { } @Override - public String createCode(String expression) throws SimpleParserException { - return BaseSimpleNode.createCode(expression, block); + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleNode.createCode(camelContext, expression, block); } } diff --git a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java index e39ab05d16c..0aeb332caa0 100644 --- a/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java +++ b/core/camel-core-languages/src/main/java/org/apache/camel/language/simple/ast/UnaryExpression.java @@ -147,14 +147,14 @@ public class UnaryExpression extends BaseSimpleNode { } @Override - public String createCode(String expression) throws SimpleParserException { - return BaseSimpleParser.CODE_START + doCreateCode(expression) + BaseSimpleParser.CODE_END; + public String createCode(CamelContext camelContext, String expression) throws SimpleParserException { + return BaseSimpleParser.CODE_START + doCreateCode(camelContext, expression) + BaseSimpleParser.CODE_END; } - private String doCreateCode(String expression) throws SimpleParserException { + private String doCreateCode(CamelContext camelContext, String expression) throws SimpleParserException { ObjectHelper.notNull(left, "left node", this); - final String number = left.createCode(expression); + final String number = left.createCode(camelContext, expression); if (operator == UnaryOperatorType.INC) { return "increment(exchange, " + number + ")"; 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 ae9bdc6147d..fbcc49b19a1 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 @@ -889,6 +889,7 @@ public class SimpleTest extends LanguageTestSupport { assertExpression("${headers}", headers); assertExpression("${in.headers}", headers); + assertExpression("${headers.size}", 2); } @Test @@ -952,6 +953,7 @@ public class SimpleTest extends LanguageTestSupport { assertEquals(3, variables.size()); assertExpression("${variables}", variables); + assertExpression("${variables.size}", 3); } @Test diff --git a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java index 8508272a029..8181e3b0e7c 100644 --- a/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java +++ b/core/camel-support/src/main/java/org/apache/camel/support/builder/ExpressionBuilder.java @@ -367,6 +367,23 @@ public class ExpressionBuilder { }; } + /** + * Returns an expression for the size of the message headers + */ + public static Expression headersSizeExpression() { + return new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + return exchange.getIn().getHeaders().size(); + } + + @Override + public String toString() { + return "headers.size"; + } + }; + } + /** * Returns an expression for the {@link Exchange} variables * @@ -386,6 +403,23 @@ public class ExpressionBuilder { }; } + /** + * Returns an expression for the number of {@link Exchange} variables + */ + public static Expression variablesSizeExpression() { + return new ExpressionAdapter() { + @Override + public Object evaluate(Exchange exchange) { + return exchange.getVariables().size(); + } + + @Override + public String toString() { + return "variables.size"; + } + }; + } + /** * Returns an expression for the exchange pattern *