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
commit dd25f057bca2ad39c321e969a7895cc574f38a4c Author: Claus Ibsen <[email protected]> AuthorDate: Fri Oct 16 15:06:20 2020 +0200 CAMEL-15697: camel-joor - Camel expression langauge using jOOR runtime java compiled. --- .../apache/camel/catalog/docs/joor-language.adoc | 3 +- .../org/apache/camel/language/joor/joor.json | 1 + .../camel-joor/src/main/docs/joor-language.adoc | 3 +- .../language/joor/JoorCompilationException.java | 38 ++++++++++++++ .../apache/camel/language/joor/JoorExpression.java | 58 +++++++++++++++++++--- .../apache/camel/language/joor/JoorLanguage.java | 14 +++++- .../language/joor/JoorTransformResourceTest.java | 50 +++++++++++++++++++ .../camel-joor/src/test/resources/myjoor.joor | 8 +++ .../org/apache/camel/model/language/joor.json | 1 + .../camel/model/language/JoorExpression.java | 15 ++++++ .../reifier/language/JoorExpressionReifier.java | 7 +-- .../java/org/apache/camel/xml/in/ModelParser.java | 1 + .../modules/languages/pages/joor-language.adoc | 3 +- 13 files changed, 188 insertions(+), 14 deletions(-) diff --git a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/joor-language.adoc b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/joor-language.adoc index f1a75c8..b3485ea 100644 --- a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/joor-language.adoc +++ b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/joor-language.adoc @@ -20,13 +20,14 @@ NOTE: Java 8 is not supported. This requires Java 11 or 14. // language options: START -The jOOR language supports 3 options, which are listed below. +The jOOR language supports 4 options, which are listed below. [width="100%",cols="2,1m,1m,6",options="header"] |=== | Name | Default | Java Type | Description +| preCompile | true | Boolean | Whether the expression should be pre compiled once during initialization phase. If this is turned off, then the expression is reloaded and compiled on each evaluation. | resultType | | String | Sets the class name of the result type (type from output) | singleQuotes | true | Boolean | Whether single quotes can be used as replacement for double quotes. This is convenient when you need to work with strings inside strings. | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks diff --git a/components/camel-joor/src/generated/resources/org/apache/camel/language/joor/joor.json b/components/camel-joor/src/generated/resources/org/apache/camel/language/joor/joor.json index 3a64a45..9d1220f 100644 --- a/components/camel-joor/src/generated/resources/org/apache/camel/language/joor/joor.json +++ b/components/camel-joor/src/generated/resources/org/apache/camel/language/joor/joor.json @@ -17,6 +17,7 @@ }, "properties": { "expression": { "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "The expression value in your chosen language syntax" }, + "preCompile": { "kind": "attribute", "displayName": "Pre Compile", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether the expression should be pre compiled once during initialization phase. If this is turned off, then the expression is reloaded and compiled on each evaluation." }, "resultType": { "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Sets the class name of the result type (type from output)" }, "singleQuotes": { "kind": "attribute", "displayName": "Single Quotes", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether single quotes can be used as replacement for double quotes. This is convenient when you need to work with strings inside strings." }, "trim": { "kind": "attribute", "displayName": "Trim", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }, diff --git a/components/camel-joor/src/main/docs/joor-language.adoc b/components/camel-joor/src/main/docs/joor-language.adoc index f1a75c8..b3485ea 100644 --- a/components/camel-joor/src/main/docs/joor-language.adoc +++ b/components/camel-joor/src/main/docs/joor-language.adoc @@ -20,13 +20,14 @@ NOTE: Java 8 is not supported. This requires Java 11 or 14. // language options: START -The jOOR language supports 3 options, which are listed below. +The jOOR language supports 4 options, which are listed below. [width="100%",cols="2,1m,1m,6",options="header"] |=== | Name | Default | Java Type | Description +| preCompile | true | Boolean | Whether the expression should be pre compiled once during initialization phase. If this is turned off, then the expression is reloaded and compiled on each evaluation. | resultType | | String | Sets the class name of the result type (type from output) | singleQuotes | true | Boolean | Whether single quotes can be used as replacement for double quotes. This is convenient when you need to work with strings inside strings. | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompilationException.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompilationException.java new file mode 100644 index 0000000..c0184c6 --- /dev/null +++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompilationException.java @@ -0,0 +1,38 @@ +/* + * 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.joor; + +public class JoorCompilationException extends RuntimeException { + + private String className; + private String code; + + public JoorCompilationException(String className, String code, Throwable cause) { + super("jOOR compilation error for class: " + className + " with code:\n" + code, cause); + this.className = className; + this.code = code; + } + + public String getClassName() { + return className; + } + + public String getCode() { + return code; + } + +} diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpression.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpression.java index e172991..d01ce92 100644 --- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpression.java +++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpression.java @@ -16,10 +16,16 @@ */ package org.apache.camel.language.joor; +import java.io.IOException; +import java.io.InputStream; + import org.apache.camel.CamelContext; import org.apache.camel.Exchange; import org.apache.camel.ExpressionEvaluationException; +import org.apache.camel.RuntimeCamelException; import org.apache.camel.support.ExpressionAdapter; +import org.apache.camel.support.ResourceHelper; +import org.apache.camel.util.IOHelper; import org.joor.Reflect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,9 +37,11 @@ public class JoorExpression extends ExpressionAdapter { private final String fqn; private final String text; + private String code; private Reflect compiled; private Class<?> resultType; + private boolean preCompile = true; private boolean singleQuotes = true; public JoorExpression(String fqn, String text) { @@ -46,6 +54,14 @@ public class JoorExpression extends ExpressionAdapter { return "joor:" + text; } + public boolean isPreCompile() { + return preCompile; + } + + public void setPreCompile(boolean preCompile) { + this.preCompile = preCompile; + } + public Class<?> getResultType() { return resultType; } @@ -73,9 +89,37 @@ public class JoorExpression extends ExpressionAdapter { } } + if (preCompile) { + this.code = evalCode(context, text); + LOG.debug(code); + this.compiled = compile(fqn, code); + } + } + + private Reflect compile(String fqn, String code) { + try { + return Reflect.compile(fqn, code); + } catch (Exception e) { + throw new JoorCompilationException(fqn, code, e); + } + } + + private String evalCode(CamelContext camelContext, String text) { String qn = fqn.substring(0, fqn.lastIndexOf('.')); String name = fqn.substring(fqn.lastIndexOf('.') + 1); + if (text.startsWith("resource:")) { + String url = text.substring(9); + try (InputStream is = ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, url)) { + text = IOHelper.loadText(is); + } catch (IOException e) { + throw RuntimeCamelException.wrapRuntimeException(e); + } + } + + // trim text + text = text.trim(); + // wrap text into a class method we can call StringBuilder sb = new StringBuilder(); sb.append("\n"); @@ -99,7 +143,7 @@ public class JoorExpression extends ExpressionAdapter { } else { sb.append(text); } - if (!text.endsWith(";")) { + if (!text.endsWith("}") && !text.endsWith(";")) { sb.append(";"); } sb.append("\n"); @@ -107,16 +151,18 @@ public class JoorExpression extends ExpressionAdapter { sb.append("}\n"); sb.append("\n"); - String code = sb.toString(); - LOG.debug(code); - - compiled = Reflect.compile(fqn, code); + return sb.toString(); } @Override public Object evaluate(Exchange exchange) { try { - Object out = compiled + Reflect ref = compiled; + if (ref == null) { + String eval = evalCode(exchange.getContext(), text); + ref = compile(fqn, eval); + } + Object out = ref .call("evaluate", exchange.getContext(), exchange, exchange.getIn(), exchange.getIn().getBody()).get(); if (out != null && resultType != null) { return exchange.getContext().getTypeConverter().convertTo(resultType, exchange, out); diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorLanguage.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorLanguage.java index 1a45547..3fdb508 100644 --- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorLanguage.java +++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorLanguage.java @@ -35,9 +35,18 @@ public class JoorLanguage extends LanguageSupport implements StaticService { private long taken; private static final AtomicInteger COUNTER = new AtomicInteger(); + private boolean preCompile = true; private Class<?> resultType; private boolean singleQuotes = true; + public boolean isPreCompile() { + return preCompile; + } + + public void setPreCompile(boolean preCompile) { + this.preCompile = preCompile; + } + public Class<?> getResultType() { return resultType; } @@ -80,8 +89,9 @@ public class JoorLanguage extends LanguageSupport implements StaticService { @Override public Expression createExpression(String expression, Object[] properties) { JoorExpression exp = new JoorExpression(nextFQN(), expression); - exp.setResultType(property(Class.class, properties, 0, resultType)); - exp.setSingleQuotes(property(boolean.class, properties, 1, singleQuotes)); + exp.setPreCompile(property(boolean.class, properties, 0, preCompile)); + exp.setResultType(property(Class.class, properties, 1, resultType)); + exp.setSingleQuotes(property(boolean.class, properties, 2, singleQuotes)); exp.init(getCamelContext()); return exp; } diff --git a/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorTransformResourceTest.java b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorTransformResourceTest.java new file mode 100644 index 0000000..38317c9 --- /dev/null +++ b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorTransformResourceTest.java @@ -0,0 +1,50 @@ +/* + * 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.joor; + +import org.apache.camel.builder.RouteBuilder; +import org.apache.camel.test.junit5.CamelTestSupport; +import org.junit.jupiter.api.Test; + +public class JoorTransformResourceTest extends CamelTestSupport { + + @Override + protected RouteBuilder createRouteBuilder() throws Exception { + return new RouteBuilder() { + @Override + public void configure() throws Exception { + from("direct:start") + .transform().joor("resource:myjoor.joor") + .to("mock:result"); + } + }; + } + + @Test + public void testTransform() throws Exception { + getMockEndpoint("mock:result").expectedBodiesReceived("A great number", "Happy riding", "An old Camel rider", + "A great number"); + + template.sendBody("direct:start", 20); + template.sendBody("direct:start", 44); + template.sendBody("direct:start", 101); + template.sendBody("direct:start", 50); + + assertMockEndpointsSatisfied(); + } + +} diff --git a/components/camel-joor/src/test/resources/myjoor.joor b/components/camel-joor/src/test/resources/myjoor.joor new file mode 100644 index 0000000..8089530 --- /dev/null +++ b/components/camel-joor/src/test/resources/myjoor.joor @@ -0,0 +1,8 @@ +int number = (int) body; +if (number % 10 == 0) { + return 'A great number'; +} else if (number > 100) { + return 'An old Camel rider'; +} else { + return 'Happy riding'; +} \ No newline at end of file diff --git a/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/joor.json b/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/joor.json index 4970e2c..94f104e 100644 --- a/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/joor.json +++ b/core/camel-core-engine/src/generated/resources/org/apache/camel/model/language/joor.json @@ -13,6 +13,7 @@ }, "properties": { "expression": { "kind": "value", "displayName": "Expression", "required": true, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "The expression value in your chosen language syntax" }, + "preCompile": { "kind": "attribute", "displayName": "Pre Compile", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether the expression should be pre compiled once during initialization phase. If this is turned off, then the expression is reloaded and compiled on each evaluation." }, "resultType": { "kind": "attribute", "displayName": "Result Type", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "secret": false, "description": "Sets the class name of the result type (type from output)" }, "singleQuotes": { "kind": "attribute", "displayName": "Single Quotes", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether single quotes can be used as replacement for double quotes. This is convenient when you need to work with strings inside strings." }, "trim": { "kind": "attribute", "displayName": "Trim", "required": false, "type": "boolean", "javaType": "java.lang.Boolean", "deprecated": false, "secret": false, "defaultValue": true, "description": "Whether to trim the value to remove leading and trailing whitespaces and line breaks" }, diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/model/language/JoorExpression.java b/core/camel-core-engine/src/main/java/org/apache/camel/model/language/JoorExpression.java index 49e17bf..0631833 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/model/language/JoorExpression.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/model/language/JoorExpression.java @@ -32,6 +32,9 @@ import org.apache.camel.spi.Metadata; @XmlAccessorType(XmlAccessType.FIELD) public class JoorExpression extends ExpressionDefinition { + @XmlAttribute + @Metadata(defaultValue = "true", javaType = "java.lang.Boolean") + private String preCompile; @XmlAttribute(name = "resultType") private String resultTypeName; @XmlTransient @@ -52,6 +55,18 @@ public class JoorExpression extends ExpressionDefinition { return "joor"; } + public String getPreCompile() { + return preCompile; + } + + /** + * Whether the expression should be pre compiled once during initialization phase. If this is turned off, then the + * expression is reloaded and compiled on each evaluation. + */ + public void setPreCompile(String preCompile) { + this.preCompile = preCompile; + } + public String getSingleQuotes() { return singleQuotes; } diff --git a/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/JoorExpressionReifier.java b/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/JoorExpressionReifier.java index 87075a5..491ae9e 100644 --- a/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/JoorExpressionReifier.java +++ b/core/camel-core-engine/src/main/java/org/apache/camel/reifier/language/JoorExpressionReifier.java @@ -43,9 +43,10 @@ public class JoorExpressionReifier extends ExpressionReifier<JoorExpression> { } private Object[] createProperties() { - Object[] properties = new Object[2]; - properties[0] = definition.getResultType(); - properties[1] = parseBoolean(definition.getSingleQuotes()); + Object[] properties = new Object[3]; + properties[0] = parseBoolean(definition.getPreCompile()); + properties[1] = definition.getResultType(); + properties[2] = parseBoolean(definition.getSingleQuotes()); return properties; } diff --git a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java index 0bec5be..a184110 100644 --- a/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java +++ b/core/camel-xml-io/src/generated/java/org/apache/camel/xml/in/ModelParser.java @@ -2349,6 +2349,7 @@ public class ModelParser extends BaseParser { protected JoorExpression doParseJoorExpression() throws IOException, XmlPullParserException { return doParse(new JoorExpression(), (def, key, val) -> { switch (key) { + case "preCompile": def.setPreCompile(val); break; case "resultType": def.setResultTypeName(val); break; case "singleQuotes": def.setSingleQuotes(val); break; default: return expressionDefinitionAttributeHandler().accept(def, key, val); diff --git a/docs/components/modules/languages/pages/joor-language.adoc b/docs/components/modules/languages/pages/joor-language.adoc index 470482f..403848e 100644 --- a/docs/components/modules/languages/pages/joor-language.adoc +++ b/docs/components/modules/languages/pages/joor-language.adoc @@ -22,13 +22,14 @@ NOTE: Java 8 is not supported. This requires Java 11 or 14. // language options: START -The jOOR language supports 3 options, which are listed below. +The jOOR language supports 4 options, which are listed below. [width="100%",cols="2,1m,1m,6",options="header"] |=== | Name | Default | Java Type | Description +| preCompile | true | Boolean | Whether the expression should be pre compiled once during initialization phase. If this is turned off, then the expression is reloaded and compiled on each evaluation. | resultType | | String | Sets the class name of the result type (type from output) | singleQuotes | true | Boolean | Whether single quotes can be used as replacement for double quotes. This is convenient when you need to work with strings inside strings. | trim | true | Boolean | Whether to trim the value to remove leading and trailing whitespaces and line breaks
