You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/05/10 06:34:45 UTC
[camel] 04/06: CAMEL-16596: ScriptingLanguage SPI.
This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch CAMEL-16593
in repository https://gitbox.apache.org/repos/asf/camel.git
commit f79c25816ad2ed9db21e031712056ff3c62d24de
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Mon May 10 08:24:47 2021 +0200
CAMEL-16596: ScriptingLanguage SPI.
---
.../apache/camel/language/joor/JoorLanguage.java | 26 ++-
.../org/apache/camel/language/joor/JoorMethod.java | 3 +
.../camel/language/joor/JoorScriptingCompiler.java | 214 +++++++++++++++++++++
...tionException.java => JoorScriptingMethod.java} | 26 +--
.../language/joor/JoorScriptingLanguageTest.java | 47 +++++
components/camel-kamelet/pom.xml | 5 +
.../kamelet/KameletLocalBeanJoorExternalTest.java | 76 ++++++++
.../src/test/resources/mybar.joor} | 18 +-
8 files changed, 379 insertions(+), 36 deletions(-)
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 9d5b883..b3788d6 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
@@ -23,7 +23,9 @@ import java.util.TreeSet;
import org.apache.camel.Expression;
import org.apache.camel.Predicate;
+import org.apache.camel.RuntimeCamelException;
import org.apache.camel.StaticService;
+import org.apache.camel.spi.ScriptingLanguage;
import org.apache.camel.spi.annotations.Language;
import org.apache.camel.support.ExpressionToPredicateAdapter;
import org.apache.camel.support.LanguageSupport;
@@ -34,12 +36,13 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@Language("joor")
-public class JoorLanguage extends LanguageSupport implements StaticService {
+public class JoorLanguage extends LanguageSupport implements ScriptingLanguage, StaticService {
private static final Logger LOG = LoggerFactory.getLogger(JoorLanguage.class);
private static Boolean java8;
private final JoorCompiler compiler = new JoorCompiler();
+ private final JoorScriptingCompiler scriptingCompiler = new JoorScriptingCompiler();
private final Set<String> imports = new TreeSet<>();
private final Map<String, String> aliases = new HashMap<>();
@@ -83,6 +86,27 @@ public class JoorLanguage extends LanguageSupport implements StaticService {
}
@Override
+ public <T> T evaluate(String script, Map<String, Object> bindings, Class<T> resultType) {
+ Object out;
+ JoorScriptingMethod target = scriptingCompiler.compile(getCamelContext(), script, bindings, resultType, singleQuotes);
+ try {
+ if (bindings != null && !bindings.isEmpty()) {
+ Object[] args = bindings.values().toArray(new Object[0]);
+ out = target.evaluate(args);
+ } else {
+ out = target.evaluate();
+ }
+ } catch (Exception e) {
+ throw RuntimeCamelException.wrapRuntimeException(e);
+ }
+ if (out != null && resultType != null) {
+ return getCamelContext().getTypeConverter().convertTo(resultType, out);
+ } else {
+ return (T) out;
+ }
+ }
+
+ @Override
public Predicate createPredicate(String expression) {
return ExpressionToPredicateAdapter.toPredicate(createExpression(expression));
}
diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java
index ade1503..a1a8c03 100644
--- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java
+++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java
@@ -22,6 +22,9 @@ import org.apache.camel.CamelContext;
import org.apache.camel.Exchange;
import org.apache.camel.Message;
+/**
+ * Interface for the compiled joor method when evaluating as Camel {@link org.apache.camel.Expression}.
+ */
public interface JoorMethod {
Object evaluate(CamelContext context, Exchange exchange, Message message, Object body, Optional<?> optionalBody)
diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java
new file mode 100644
index 0000000..6527dd7
--- /dev/null
+++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingCompiler.java
@@ -0,0 +1,214 @@
+/*
+ * 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 java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.StaticService;
+import org.apache.camel.support.CamelContextHelper;
+import org.apache.camel.support.ScriptHelper;
+import org.apache.camel.support.service.ServiceSupport;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.StopWatch;
+import org.joor.Reflect;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class JoorScriptingCompiler extends ServiceSupport implements StaticService {
+
+ private static final Pattern BEAN_INJECTION_PATTERN = Pattern.compile("(#bean:)([A-Za-z0-9-_]*)");
+
+ private static final Logger LOG = LoggerFactory.getLogger(JoorCompiler.class);
+ private static final AtomicInteger UUID = new AtomicInteger();
+ private Set<String> imports = new TreeSet<>();
+ private Map<String, String> aliases;
+ private int counter;
+ private long taken;
+
+ public Set<String> getImports() {
+ return imports;
+ }
+
+ public void setImports(Set<String> imports) {
+ this.imports = imports;
+ }
+
+ public Map<String, String> getAliases() {
+ return aliases;
+ }
+
+ public void setAliases(Map<String, String> aliases) {
+ this.aliases = aliases;
+ }
+
+ @Override
+ protected void doStop() throws Exception {
+ super.doStop();
+ if (counter > 0) {
+ LOG.info("jOOR scripting language compiled {} scripts in {} millis", counter, taken);
+ }
+ }
+
+ public JoorScriptingMethod compile(
+ CamelContext camelContext, String script, Map<String, Object> bindings, Class<?> resultType, boolean singleQuotes) {
+ StopWatch watch = new StopWatch();
+
+ JoorScriptingMethod answer;
+ String className = nextFQN();
+ String code = evalCode(camelContext, className, script, bindings, resultType, singleQuotes);
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Compiling code:\n\n{}\n", code);
+ }
+ Reflect ref = Reflect.compile(className, code);
+ Class<?> clazz = ref.type();
+ answer = (JoorScriptingMethod) clazz.getConstructor(CamelContext.class).newInstance(camelContext);
+ } catch (Exception e) {
+ throw new JoorCompilationException(className, code, e);
+ }
+
+ counter++;
+ taken += watch.taken();
+ return answer;
+ }
+
+ @SuppressWarnings("unchecked")
+ public String evalCode(
+ CamelContext camelContext, String fqn, String script, Map<String, Object> bindings, Class<?> resultType,
+ boolean singleQuotes) {
+ String qn = fqn.substring(0, fqn.lastIndexOf('.'));
+ String name = fqn.substring(fqn.lastIndexOf('.') + 1);
+
+ // reload script
+ script = ScriptHelper.resolveOptionalExternalScript(camelContext, script);
+
+ // trim text
+ script = script.trim();
+
+ Set<String> scriptImports = new LinkedHashSet<>();
+ Map<String, Class> scriptBeans = new HashMap<>();
+ script = evalDependencyInjection(camelContext, scriptImports, scriptBeans, script);
+
+ // wrap text into a class method we can call
+ StringBuilder sb = new StringBuilder();
+ sb.append("package ").append(qn).append(";\n");
+ sb.append("\n");
+ sb.append("import java.util.*;\n");
+ sb.append("import java.util.concurrent.*;\n");
+ sb.append("import java.util.stream.*;\n");
+ sb.append("\n");
+ sb.append("import org.apache.camel.*;\n");
+ sb.append("import org.apache.camel.util.*;\n");
+ sb.append("import static org.apache.camel.language.joor.JoorHelper.*;\n");
+ sb.append("\n");
+ // custom imports
+ for (String i : imports) {
+ sb.append(i);
+ if (!i.endsWith(";")) {
+ sb.append(";");
+ }
+ sb.append("\n");
+ }
+ for (String i : scriptImports) {
+ sb.append("import ");
+ sb.append(i);
+ sb.append(";\n");
+ }
+ sb.append("\n");
+ sb.append("public class ").append(name).append(" implements org.apache.camel.language.joor.JoorScriptingMethod {\n");
+ sb.append("\n");
+ // local beans variables
+ for (Map.Entry<String, Class> entry : scriptBeans.entrySet()) {
+ sb.append(" private ").append(entry.getValue().getSimpleName()).append(" ").append(entry.getKey()).append(";\n");
+ }
+ sb.append("\n");
+
+ // constructor to lookup beans
+ sb.append(" public ").append(name).append("(CamelContext context) throws Exception {\n");
+ for (Map.Entry<String, Class> entry : scriptBeans.entrySet()) {
+ sb.append(" ").append(entry.getKey()).append(" = ").append("context.getRegistry().lookupByNameAndType(\"")
+ .append(entry.getKey()).append("\", ").append(entry.getValue().getSimpleName()).append(".class);\n");
+ }
+ sb.append(" }\n");
+ sb.append("\n");
+
+ sb.append(" @Override\n");
+ sb.append(
+ " public Object evaluate(Object... args) throws Exception {\n");
+ sb.append(" ");
+
+ // bindings as local variables
+ if (bindings != null) {
+ int i = 0;
+ for (Map.Entry<String, Object> bind : bindings.entrySet()) {
+ String vn = bind.getKey();
+ String cn = ObjectHelper.className(bind.getValue());
+ String b = String.format(" var %s = (%s) args[%s];\n", vn, cn, i++);
+ sb.append(b);
+ }
+ sb.append("\n");
+ }
+ if (!script.contains("return ")) {
+ sb.append("return ");
+ }
+
+ if (singleQuotes) {
+ // single quotes instead of double quotes, as its very annoying for string in strings
+ String quoted = script.replace('\'', '"');
+ sb.append(quoted);
+ } else {
+ sb.append(script);
+ }
+ if (!script.endsWith("}") && !script.endsWith(";")) {
+ sb.append(";");
+ }
+ sb.append("\n");
+ sb.append(" }\n");
+ sb.append("}\n");
+ sb.append("\n");
+
+ return sb.toString();
+ }
+
+ private String evalDependencyInjection(
+ CamelContext camelContext, Set<String> scriptImports, Map<String, Class> scriptBeans, String script) {
+ Matcher matcher = BEAN_INJECTION_PATTERN.matcher(script);
+ while (matcher.find()) {
+ String id = matcher.group(2);
+ Object bean = CamelContextHelper.mandatoryLookup(camelContext, id);
+ Class<?> type = bean.getClass();
+ scriptImports.add(type.getName());
+ scriptBeans.put(id, type);
+ script = matcher.replaceFirst(id);
+ matcher = BEAN_INJECTION_PATTERN.matcher(script);
+ }
+ return script;
+ }
+
+ private static String nextFQN() {
+ return "org.apache.camel.language.joor.compiled.scripting.JoorScripting" + UUID.incrementAndGet();
+ }
+
+}
diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpressionEvaluationException.java b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingMethod.java
similarity index 52%
rename from components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpressionEvaluationException.java
rename to components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingMethod.java
index 806e550..2d2eec6 100644
--- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorExpressionEvaluationException.java
+++ b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorScriptingMethod.java
@@ -16,27 +16,11 @@
*/
package org.apache.camel.language.joor;
-import org.apache.camel.Exchange;
-import org.apache.camel.Expression;
-import org.apache.camel.ExpressionEvaluationException;
-
-public class JoorExpressionEvaluationException extends ExpressionEvaluationException {
-
- private final String className;
- private final String code;
-
- public JoorExpressionEvaluationException(Expression expression, String className, String code, Exchange exchange,
- Throwable cause) {
- super(expression, "jOOR evaluation error for class: " + className + " with code:\n" + code, exchange, cause);
- this.className = className;
- this.code = code;
- }
+/**
+ * Interface for the compiled joor scripting method when evaluating as scripting language.
+ */
+public interface JoorScriptingMethod {
- public String getClassName() {
- return className;
- }
+ Object evaluate(Object... args) throws Exception;
- public String getCode() {
- return code;
- }
}
diff --git a/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorScriptingLanguageTest.java b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorScriptingLanguageTest.java
new file mode 100644
index 0000000..97f00f0
--- /dev/null
+++ b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorScriptingLanguageTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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.spi.Language;
+import org.apache.camel.spi.ScriptingLanguage;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+public class JoorScriptingLanguageTest extends CamelTestSupport {
+
+ @Test
+ public void testScripting() throws Exception {
+ Language lan = context.resolveLanguage("joor");
+ Assertions.assertTrue(lan instanceof ScriptingLanguage);
+
+ ScriptingLanguage slan = (ScriptingLanguage) lan;
+ int num = slan.evaluate("2 * 3", null, int.class);
+ Assertions.assertEquals(6, num);
+
+ MyUser user = new MyUser();
+ user.setAge(33);
+ user.setName("Scott");
+ Map<String, Object> bindings = new LinkedHashMap<>();
+ bindings.put("user", user);
+ String out = slan.evaluate("'Hello ' + user.getName() + ' you are ' + user.getAge() + ' years old'", bindings, String.class);
+ Assertions.assertEquals("Hello Scott you are 33 years old", out);
+ }
+}
diff --git a/components/camel-kamelet/pom.xml b/components/camel-kamelet/pom.xml
index c443946..8cd7354 100644
--- a/components/camel-kamelet/pom.xml
+++ b/components/camel-kamelet/pom.xml
@@ -51,6 +51,11 @@
</dependency>
<dependency>
<groupId>org.apache.camel</groupId>
+ <artifactId>camel-joor</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.camel</groupId>
<artifactId>camel-timer</artifactId>
<scope>test</scope>
</dependency>
diff --git a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanJoorExternalTest.java b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanJoorExternalTest.java
new file mode 100644
index 0000000..349587d
--- /dev/null
+++ b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletLocalBeanJoorExternalTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.component.kamelet;
+
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.apache.http.annotation.Obsolete;
+import org.junit.jupiter.api.Test;
+
+public class KameletLocalBeanJoorExternalTest extends CamelTestSupport {
+
+ @Test
+ public void testOne() throws Exception {
+ getMockEndpoint("mock:result").expectedBodiesReceived("Hi John we are going to Moes");
+
+ template.sendBody("direct:moe", "John");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ @Test
+ public void testTwo() throws Exception {
+ getMockEndpoint("mock:result").expectedBodiesReceived("Hi Jack we are going to Shamrock",
+ "Hi Mary we are going to Moes");
+
+ template.sendBody("direct:shamrock", "Jack");
+ template.sendBody("direct:moe", "Mary");
+
+ assertMockEndpointsSatisfied();
+ }
+
+ // **********************************************
+ //
+ // test set-up
+ //
+ // **********************************************
+
+ @Obsolete
+ protected RoutesBuilder createRouteBuilder() throws Exception {
+ return new RouteBuilder() {
+ @Override
+ public void configure() throws Exception {
+ routeTemplate("whereTo")
+ .templateParameter("bar") // name of bar
+ .templateBean("myBar", "joor", "resource:classpath:mybar.joor")
+ .from("kamelet:source")
+ // must use {{myBar}} to refer to the local bean
+ .to("bean:{{myBar}}");
+
+ from("direct:shamrock")
+ .kamelet("whereTo?bar=Shamrock")
+ .to("mock:result");
+
+ from("direct:moe")
+ .kamelet("whereTo?bar=Moes")
+ .to("mock:result");
+ }
+ };
+ }
+
+}
diff --git a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java b/components/camel-kamelet/src/test/resources/mybar.joor
similarity index 69%
copy from components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java
copy to components/camel-kamelet/src/test/resources/mybar.joor
index ade1503..f9c0bc9 100644
--- a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorMethod.java
+++ b/components/camel-kamelet/src/test/resources/mybar.joor
@@ -14,17 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.camel.language.joor;
-
-import java.util.Optional;
-
-import org.apache.camel.CamelContext;
-import org.apache.camel.Exchange;
-import org.apache.camel.Message;
-
-public interface JoorMethod {
-
- Object evaluate(CamelContext context, Exchange exchange, Message message, Object body, Optional<?> optionalBody)
- throws Exception;
-
-}
+var bean = new org.apache.camel.component.kamelet.MyInjectBar();
+// context is RouteTemplateContext
+bean.setBar(context.getProperty('bar', String.class));
+return bean;