This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/master by this push: new fa12252 CAMEL-15704: camel-csimple - Compiled simple language. fa12252 is described below commit fa12252f98108f5ead734245256daade1fea3feb Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Mon Nov 30 11:06:18 2020 +0100 CAMEL-15704: camel-csimple - Compiled simple language. --- .../language/csimple/joor/OriginalSimpleTest.java | 72 +++++--- .../camel/language/csimple/CSimpleHelper.java | 111 +++++++++---- .../simple/ast/SimpleFunctionExpression.java | 181 ++++++++++++++++----- 3 files changed, 267 insertions(+), 97 deletions(-) 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 8b29d79..4d586df 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 @@ -406,23 +406,56 @@ public class OriginalSimpleTest extends LanguageTestSupport { @Test public void testOGNLBodyListAndMapAndMethod() throws Exception { - Map<String, Object> map = new HashMap<>(); + Map<String, OrderLine> map = new HashMap<>(); map.put("camel", new OrderLine(123, "Camel in Action")); map.put("amq", new OrderLine(456, "ActiveMQ in Action")); - List<Map<String, Object>> lines = new ArrayList<>(); + List<Map<String, OrderLine>> lines = new ArrayList<>(); + lines.add(map); + + exchange.getIn().setBody(lines); + + assertExpression("${bodyAsIndex(OrderLine, '[0][camel]').id}", 123); + assertExpression("${bodyAsIndex(OrderLine, '[0][camel]').name}", "Camel in Action"); + assertExpression("${bodyAsIndex(OrderLine, '[0][camel]').getId}", 123); + assertExpression("${bodyAsIndex(OrderLine, '[0][camel]').getName}", "Camel in Action"); + + assertExpression("${bodyAs(OrderLine)[0][camel].id}", 123); + assertExpression("${bodyAs(OrderLine)[0][camel].name}", "Camel in Action"); + assertExpression("${bodyAs(OrderLine)[0][camel].getId}", 123); + assertExpression("${bodyAs(OrderLine)[0][camel].getName}", "Camel in Action"); + } + + @Test + public void testOGNLMandatoryBodyListAndMapAndMethod() throws Exception { + Map<String, OrderLine> map = new HashMap<>(); + map.put("camel", new OrderLine(123, "Camel in Action")); + map.put("amq", new OrderLine(456, "ActiveMQ in Action")); + map.put("noname", new OrderLine(789, null)); + + List<Map<String, OrderLine>> lines = new ArrayList<>(); lines.add(map); exchange.getIn().setBody(lines); - assertExpression("${in.body[0][camel].id}", 123); - assertExpression("${in.body[0][camel].name}", "Camel in Action"); - assertExpression("${in.body[0][camel].getId}", 123); - assertExpression("${in.body[0][camel].getName}", "Camel in Action"); - assertExpression("${body[0][camel].id}", 123); - assertExpression("${body[0][camel].name}", "Camel in Action"); - assertExpression("${body[0][camel].getId}", 123); - assertExpression("${body[0][camel].getName}", "Camel in Action"); + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][camel]').id}", 123); + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][camel]').name}", "Camel in Action"); + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][camel]').getId}", 123); + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][camel]').getName}", "Camel in Action"); + + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][noname]').getId}", 789); + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][noname]').getName}", null); + try { + assertExpression("${mandatoryBodyAsIndex(OrderLine, '[0][doesnotexists]').getName}", null); + fail("Should throw exception"); + } catch (Exception e) { + assertIsInstanceOf(InvalidPayloadException.class, e.getCause()); + } + + assertExpression("${mandatoryBodyAs(OrderLine)[0][camel].id}", 123); + assertExpression("${mandatoryBodyAs(OrderLine)[0][camel].name}", "Camel in Action"); + assertExpression("${mandatoryBodyAs(OrderLine)[0][camel].getId}", 123); + assertExpression("${mandatoryBodyAs(OrderLine)[0][camel].getName}", "Camel in Action"); } @Test @@ -517,14 +550,7 @@ public class OriginalSimpleTest extends LanguageTestSupport { @Test public void testOGNLPropertyMapNotMap() throws Exception { - try { - assertExpression("${exchangeProperty.foobar[bar]}", null); - fail("Should have thrown an exception"); - } catch (RuntimeBeanExpressionException e) { - IndexOutOfBoundsException cause = assertIsInstanceOf(IndexOutOfBoundsException.class, e.getCause()); - assertEquals("Key: bar not found in bean: cba of type: java.lang.String using OGNL path [[bar]]", - cause.getMessage()); - } + assertExpression("${exchangeProperty.foobar[bar]}", null); } @Test @@ -565,12 +591,12 @@ public class OriginalSimpleTest extends LanguageTestSupport { assertPredicate("${header.beer} == \" \"", true); assertPredicate("${header.beer} == \" \"", true); - assertPredicate("${header.beer.toString().trim()} == \"\"", true); - assertPredicate("${header.beer.toString().trim()} == \"\"", true); + assertPredicate("${headerAs(beer, String).toString().trim()} == \"\"", true); + assertPredicate("${headerAs(beer, String).toString().trim()} == \"\"", true); exchange.getIn().setHeader("beer", " "); - assertPredicate("${header.beer.trim()} == \"\"", true); - assertPredicate("${header.beer.trim()} == \"\"", true); + assertPredicate("${headerAs(beer, String).trim()} == \"\"", true); + assertPredicate("${headerAs(beer, String).trim()} == \"\"", true); } @Test @@ -1073,7 +1099,7 @@ public class OriginalSimpleTest extends LanguageTestSupport { exchange.setProperty(Exchange.EXCEPTION_CAUGHT, new CamelAuthorizationException("The camel authorization exception", exchange)); - assertExpression("${exception.getPolicyId}", "myPolicy"); + assertExpression("${exceptionAs(org.apache.camel.CamelAuthorizationException).getPolicyId}", "myPolicy"); } @Test 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 654eab8..427e390 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 @@ -49,6 +49,7 @@ import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; import org.apache.camel.util.InetAddressUtil; import org.apache.camel.util.ObjectHelper; +import org.apache.camel.util.OgnlHelper; import org.apache.camel.util.SkipIterator; import org.apache.camel.util.StringHelper; import org.apache.camel.util.TimeUtils; @@ -82,37 +83,32 @@ public final class CSimpleHelper { } public static <T> T bodyAsIndex(Message message, Class<T> type, String key) { - // TODO: array - // TODO: key can have multiple elements [0][foo] or [0][0] - Object body = message.getBody(); - if (body instanceof Map) { - Map map = (Map) body; - body = map.get(key); - } else if (body instanceof List) { - List list = (List) body; - Integer num; - if (key.startsWith("last")) { - num = list.size() - 1; - - // maybe its an expression to subtract a number after last - String after = StringHelper.after(key, "-"); - if (after != null) { - Integer redux - = message.getExchange().getContext().getTypeConverter().tryConvertTo(Integer.class, after.trim()); - if (redux != null) { - num -= redux; - } else { - throw new ExpressionIllegalSyntaxException(key); - } - } - } else { - num = message.getExchange().getContext().getTypeConverter().tryConvertTo(Integer.class, key); - } - if (num != null && num >= 0 && !list.isEmpty() && list.size() > num - 1) { - body = list.get(num); + Object obj = message.getBody(); + // the key may contain multiple keys ([0][foo]) so we need to walk these keys + List<String> keys = OgnlHelper.splitOgnl(key); + for (String k : keys) { + if (k.startsWith("[") && k.endsWith("]")) { + k = StringHelper.between(k, "[", "]"); } + obj = doObjectAsIndex(message.getExchange().getContext(), obj, k); + } + return type.cast(obj); + } + + public static <T> T mandatoryBodyAsIndex(Message message, Class<T> type, int key) throws InvalidPayloadException { + T out = bodyAsIndex(message, type, "" + key); + if (out == null) { + throw new InvalidPayloadException(message.getExchange(), type, message); } - return type.cast(body); + return out; + } + + public static <T> T mandatoryBodyAsIndex(Message message, Class<T> type, String key) throws InvalidPayloadException { + T out = bodyAsIndex(message, type, key); + if (out == null) { + throw new InvalidPayloadException(message.getExchange(), type, message); + } + return out; } public static Object header(Message message, String name) { @@ -123,6 +119,19 @@ public final class CSimpleHelper { return message.getHeader(name, type); } + public static <T> T headerAsIndex(Message message, Class<T> type, String name, String key) { + Object obj = message.getHeader(name); + // the key may contain multiple keys ([0][foo]) so we need to walk these keys + List<String> keys = OgnlHelper.splitOgnl(key); + for (String k : keys) { + if (k.startsWith("[") && k.endsWith("]")) { + k = StringHelper.between(k, "[", "]"); + } + obj = doObjectAsIndex(message.getExchange().getContext(), obj, k); + } + return type.cast(obj); + } + public static Object exchangeProperty(Exchange exchange, String name) { return exchange.getProperty(name); } @@ -131,6 +140,19 @@ public final class CSimpleHelper { return exchange.getProperty(name, type); } + public static <T> T exchangePropertyAsIndex(Exchange exchange, Class<T> type, String name, String key) { + Object obj = exchange.getProperty(name); + // the key may contain multiple keys ([0][foo]) so we need to walk these keys + List<String> keys = OgnlHelper.splitOgnl(key); + for (String k : keys) { + if (k.startsWith("[") && k.endsWith("]")) { + k = StringHelper.between(k, "[", "]"); + } + obj = doObjectAsIndex(exchange.getContext(), obj, k); + } + return type.cast(obj); + } + public static String bodyOneLine(Exchange exchange) { String body = exchange.getIn().getBody(String.class); if (body == null) { @@ -632,4 +654,35 @@ public final class CSimpleHelper { return type.isInstance(leftValue); } + private static Object doObjectAsIndex(CamelContext context, Object obj, String key) { + if (obj instanceof Map) { + Map map = (Map) obj; + obj = map.get(key); + } else if (obj instanceof List) { + List list = (List) obj; + Integer num; + if (key.startsWith("last")) { + num = list.size() - 1; + + // maybe its an expression to subtract a number after last + String after = StringHelper.after(key, "-"); + if (after != null) { + Integer redux + = context.getTypeConverter().tryConvertTo(Integer.class, after.trim()); + if (redux != null) { + num -= redux; + } else { + throw new ExpressionIllegalSyntaxException(key); + } + } + } else { + num = context.getTypeConverter().tryConvertTo(Integer.class, key); + } + if (num != null && num >= 0 && !list.isEmpty() && list.size() > num - 1) { + obj = list.get(num); + } + } + return obj; + } + } 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 ba986e5..becf312 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 @@ -17,6 +17,7 @@ package org.apache.camel.language.simple.ast; import java.net.URISyntaxException; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -643,22 +644,35 @@ public class SimpleFunctionExpression extends LiteralExpression { if (remainder.startsWith("[") && remainder.endsWith("]")) { remainder = remainder.substring(1, remainder.length() - 1); } + // remove quotes from key + String key = StringHelper.removeLeadingAndEndingQuotes(remainder); + key = key.trim(); // validate syntax - boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder); + boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(key); if (invalid) { - // must use exchangePropertyAs as we need to be typed throw new SimpleParserException( - "Valid syntax: ${exchangePropertyAs(key, type).OGNL} was: " + function, token.getIndex()); + "Valid syntax: ${exchangeProperty.name[key]} was: " + function, token.getIndex()); } - if (OgnlHelper.isValidOgnlExpression(remainder)) { - // must use exchangePropertyAs as we need to be typed + // it is an index? + String index = null; + if (key.endsWith("]")) { + index = StringHelper.between(key, "[", "]"); + if (index != null) { + key = StringHelper.before(key, "["); + } + } + if (index != null) { + index = StringHelper.removeLeadingAndEndingQuotes(index); + return "exchangePropertyAsIndex(exchange, Object.class, \"" + key + "\", \"" + index + "\")"; + } else if (OgnlHelper.isValidOgnlExpression(remainder)) { + // ognl based exchange property must be typed throw new SimpleParserException( - "Valid syntax: ${exchangePropertyAs(key, type).OGNL} was: " + function, token.getIndex()); + "Valid syntax: ${exchangePropertyAs(key, type)} was: " + function, token.getIndex()); } else { // regular property - return "exchangeProperty(exchange, \"" + remainder + "\")"; + return "exchangeProperty(exchange, \"" + key + "\")"; } } @@ -849,7 +863,7 @@ public class SimpleFunctionExpression extends LiteralExpression { return null; } - private String createCodeBodyOrHeader(String function) { + private String createCodeBodyOrHeader(final String function) { // bodyAsIndex String remainder = ifStartsWithReturnRemainder("bodyAsIndex(", function); if (remainder != null) { @@ -905,25 +919,61 @@ public class SimpleFunctionExpression extends LiteralExpression { if (invalid) { throw new SimpleParserException("Valid syntax: ${bodyAs(type).OGNL} was: " + function, token.getIndex()); } - // it is an index? - String index = null; - String after = null; if (remainder.startsWith("[")) { - // the OGNL starts with an index so lets use bodyAsIndex - index = StringHelper.between(remainder, "[", "]"); - after = StringHelper.after(remainder, "]"); - } - if (index != null) { - index = StringHelper.removeLeadingAndEndingQuotes(index); - return "bodyAsIndex(message, " + type + ", \"" + index + "\")" + ognlCodeMethods(after, type); - } else { - return "bodyAs(message, " + type + ")" + ognlCodeMethods(remainder, type); + // is there any index, then we should use bodyAsIndex function instead + // (use splitOgnl which assembles multiple indexes into a single part) + List<String> parts = splitOgnl(remainder); + if (!parts.isEmpty()) { + String func = "bodyAsIndex(" + type + ", \"" + parts.remove(0) + "\")"; + String last = String.join("", parts); + if (!last.isEmpty()) { + func += "." + last; + } + return createCodeBodyOrHeader(func); + } } + return "bodyAs(message, " + type + ")" + ognlCodeMethods(remainder, type); } else { return "bodyAs(message, " + type + ")"; } } + // mandatoryBodyAsIndex + remainder = ifStartsWithReturnRemainder("mandatoryBodyAsIndex(", function); + if (remainder != null) { + String typeAndIndex = StringHelper.before(remainder, ")"); + if (typeAndIndex == null) { + throw new SimpleParserException( + "Valid syntax: ${mandatoryBodyAsIndex(type, index).OGNL} was: " + function, token.getIndex()); + } + + String type = StringHelper.before(typeAndIndex, ","); + String index = StringHelper.after(typeAndIndex, ","); + remainder = StringHelper.after(remainder, ")"); + if (ObjectHelper.isEmpty(type) || ObjectHelper.isEmpty(index)) { + throw new SimpleParserException( + "Valid syntax: ${mandatoryBodyAsIndex(type, index).OGNL} was: " + function, token.getIndex()); + } + type = StringHelper.removeQuotes(type); + type = type.trim(); + if (!type.endsWith(".class")) { + type = type + ".class"; + } + type = type.replace('$', '.'); + index = StringHelper.removeQuotes(index); + index = index.trim(); + if (ObjectHelper.isNotEmpty(remainder)) { + boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder); + if (invalid) { + throw new SimpleParserException( + "Valid syntax: ${mandatoryBodyAsIndex(type, index).OGNL} was: " + function, token.getIndex()); + } + return "mandatoryBodyAsIndex(message, " + type + ", \"" + index + "\")" + ognlCodeMethods(remainder, type); + } else { + return "mandatoryBodyAsIndex(message, " + type + ", \"" + index + "\")"; + } + } + // mandatoryBodyAs remainder = ifStartsWithReturnRemainder("mandatoryBodyAs(", function); if (remainder != null) { @@ -939,12 +989,24 @@ public class SimpleFunctionExpression extends LiteralExpression { type = type.trim(); remainder = StringHelper.after(remainder, ")"); if (ObjectHelper.isNotEmpty(remainder)) { - // TODO: mandatoryBodyAsIndex boolean invalid = OgnlHelper.isInvalidValidOgnlExpression(remainder); if (invalid) { throw new SimpleParserException( "Valid syntax: ${mandatoryBodyAs(type).OGNL} was: " + function, token.getIndex()); } + if (remainder.startsWith("[")) { + // is there any index, then we should use mandatoryBodyAsIndex function instead + // (use splitOgnl which assembles multiple indexes into a single part) + List<String> parts = splitOgnl(remainder); + if (!parts.isEmpty()) { + String func = "mandatoryBodyAsIndex(" + type + ", \"" + parts.remove(0) + "\")"; + String last = String.join("", parts); + if (!last.isEmpty()) { + func += "." + last; + } + return createCodeBodyOrHeader(func); + } + } return "mandatoryBodyAs(message, " + type + ")" + ognlCodeMethods(remainder, type); } else { return "mandatoryBodyAs(message, " + type + ")"; @@ -963,12 +1025,20 @@ public class SimpleFunctionExpression extends LiteralExpression { if (invalid) { throw new SimpleParserException("Valid syntax: ${body.OGNL} was: " + function, token.getIndex()); } - String answer = ognlCodeMethods(remainder, null); - if (answer.startsWith("body")) { - return answer; - } else { - return "body" + answer; + if (remainder.startsWith("[")) { + // is there any index, then we should use bodyAsIndex function instead + // (use splitOgnl which assembles multiple indexes into a single part) + List<String> parts = splitOgnl(remainder); + if (!parts.isEmpty()) { + String func = "bodyAsIndex(Object.class, \"" + parts.remove(0) + "\")"; + String last = String.join("", parts); + if (!last.isEmpty()) { + func += "." + last; + } + return createCodeBodyOrHeader(func); + } } + return "body" + ognlCodeMethods(remainder, null); } // headerAs @@ -1030,8 +1100,17 @@ public class SimpleFunctionExpression extends LiteralExpression { if (invalid) { throw new SimpleParserException("Valid syntax: ${header.name[key]} was: " + function, token.getIndex()); } - - if (OgnlHelper.isValidOgnlExpression(key)) { + // it is an index? + String index = null; + if (key.endsWith("]")) { + index = StringHelper.between(key, "[", "]"); + if (index != null) { + key = StringHelper.before(key, "["); + } + } + if (index != null) { + return "headerAsIndex(message, Object.class, \"" + key + "\", \"" + index + "\")"; + } else if (OgnlHelper.isValidOgnlExpression(key)) { // ognl based header must be typed throw new SimpleParserException("Valid syntax: ${headerAs(key, type)} was: " + function, token.getIndex()); } else { @@ -1139,11 +1218,37 @@ public class SimpleFunctionExpression extends LiteralExpression { return null; } + private static List<String> splitOgnl(String remainder) { + List<String> methods = OgnlHelper.splitOgnl(remainder); + // if its a double index [foo][0] then we want them combined into a single element + List<String> answer = new ArrayList<>(); + for (String m : methods) { + if (m.startsWith(".")) { + m = m.substring(1); + } + boolean index = m.startsWith("[") && m.endsWith("]"); + if (index) { + String last = answer.isEmpty() ? null : answer.get(answer.size() - 1); + boolean lastIndex = last != null && last.startsWith("[") && last.endsWith("]"); + if (lastIndex) { + String line = last + m; + answer.set(answer.size() - 1, line); + } else { + answer.add(m); + } + } else { + answer.add(m); + } + } + + return answer; + } + private static String ognlCodeMethods(String remainder, String type) { StringBuilder sb = new StringBuilder(); if (remainder != null) { - List<String> methods = OgnlHelper.splitOgnl(remainder); + List<String> methods = splitOgnl(remainder); for (int i = 0; i < methods.size(); i++) { String m = methods.get(i); if (m.startsWith("(")) { @@ -1151,12 +1256,9 @@ public class SimpleFunctionExpression extends LiteralExpression { sb.append(m); continue; } - if (m.startsWith(".")) { - m = m.substring(1); - } // clip index - String index = StringHelper.between(m, "[", "]"); + String index = StringHelper.betweenOuterPair(m, '[', ']'); if (index != null) { m = StringHelper.before(m, "["); } @@ -1182,18 +1284,7 @@ public class SimpleFunctionExpression extends LiteralExpression { // append index via a get method - eg get for a list, or get for a map (array not supported) if (index != null) { - // if there was no method then its direct on the body, so use bodyAsIndex instead of get - if (m == null || m.isEmpty()) { - sb.append("bodyAsIndex(message, "); - if (type != null) { - sb.append(type); - } else { - sb.append("Object.class"); - } - sb.append(", "); - } else { - sb.append(".get("); - } + sb.append(".get("); try { long lon = Long.parseLong(index); sb.append(lon);