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 56e988a  CAMEL-15697: camel-joor - Camel expression langauge using 
jOOR runtime java compiled.
56e988a is described below

commit 56e988a0d8e839e3d56a6c4357e9760c3b584737
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Oct 19 11:18:55 2020 +0200

    CAMEL-15697: camel-joor - Camel expression langauge using jOOR runtime java 
compiled.
---
 .../apache/camel/catalog/docs/joor-language.adoc   | 105 +++++++++++++++++++++
 .../camel-joor/src/main/docs/joor-language.adoc    | 105 +++++++++++++++++++++
 .../apache/camel/language/joor/JoorCompiler.java   |  79 ++++++++++++++++
 .../org/apache/camel/language/joor/JoorHelper.java |  41 ++++++++
 .../apache/camel/language/joor/JoorLanguage.java   |  83 +++++++++++++++-
 .../camel/language/joor/JoorCustomAliasTest.java   |  67 +++++++++++++
 .../camel/language/joor/JoorCustomImportsTest.java |  67 +++++++++++++
 .../camel/language/joor/JoorLanguageTest.java      |  56 +++++++++++
 .../org/apache/camel/language/joor/MyUser.java     |  39 ++++++++
 .../camel/impl/engine/AbstractCamelContext.java    |   6 +-
 .../org/apache/camel/support/ResourceHelper.java   |  42 +++++++++
 .../org/apache/camel/support/ScriptHelper.java     |  52 +++++++---
 .../modules/languages/pages/joor-language.adoc     | 105 +++++++++++++++++++++
 13 files changed, 832 insertions(+), 15 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 7fccfdd2..7e490dc 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
@@ -47,6 +47,111 @@ The jOOR language allows the following variables to be used 
in the script:
 | body | Object | The message body
 |===
 
+== Functions
+
+The jOOR language allows the following functions to be used in the script:
+
+[width="100%",cols="2,1m,7",options="header"]
+|===
+| Function | Description
+| bodyAs(type) | To convert the body to the given type
+| headerAs(name, type) | To convert the header with the name to the given type
+| exchangePropertyAs(name, type) | To convert the exchange property with the 
name to the given type
+|===
+
+These functions are convenient for getting the message body, header or 
exchange properties as a specific Java type.
+
+Here we want to get the message body as a `com.foo.MyUser` type we can do as 
follows:
+[source,java]
+----
+var user = bodyAs(com.foo.MyUser.class);
+----
+
+You can omit _.class_ to make the function a little bit smaller:
+[source,java]
+----
+var user = bodyAs(com.foo.MyUser);
+----
+
+The type must a fully qualified class type, but that can be inconvenient to 
type all the time, so you can configure in
+the `camel-joor.properties` file an import for the type such as:
+
+[source,properties]
+----
+import com.foo.MyUser;
+----
+
+And then the function can be shortened:
+[source,java]
+----
+var user = bodyAs(MyUser);
+----
+
+== Auto imports
+
+The jOOR language will automatic import from:
+
+[source,java]
+----
+import java.util.*;
+import java.util.concurrent.*;
+import org.apache.camel.*;
+import org.apache.camel.util.*;
+----
+
+== Configuration file
+
+You can configure the jOOR language in the `camel-joor.properties` file which 
by default is loaded from the root classpath.
+You can specify a different location with the `configResource` option on the 
jOOR language.
+
+For example you can add additional imports in the `camel-joor.properties` file 
by adding:
+
+[source,properties]
+----
+import com.foo.MyUser;
+import com.bar.*;
+import static com.foo.MyHelper.*;
+----
+
+You can also add aliases (key=value) where an alias will be used as a 
shorthand replacement in the code.
+
+[source,properties]
+----
+echo()=bodyAs(String) + bodyAs(String)
+----
+
+Which allows to use _echo()_ in the jOOR language script such as:
+
+[source,java]
+----
+from("direct:hello")
+    .transform(joor("'Hello ' + echo()"))
+    .log("You said ${body}");
+----
+
+The _echo()_ alias will be replaced with its value resulting in a script as:
+
+[source,java]
+----
+    .transform(joor("'Hello ' + bodyAs(String) + bodyAs(String)"))
+----
+
+You can configure a custom configuration location for the 
`camel-joor.properties` file or reference to a bean in the registry:
+
+[source,java]
+----
+JoorLanguage joor = (JoorLanguage) context.resolveLanguage("joor");
+joor.setConfigResource("ref:MyJoorConfig");
+----
+
+And then register a bean in the registry with id `MyJoorConfig` that is a 
String value with the content.
+
+[source,java]
+----
+String config = "....";
+camelContext.getRegistry().put("MyJoorConfig", config);
+----
+
 == Sample
 
 For example to transform the message using jOOR language to upper case
diff --git a/components/camel-joor/src/main/docs/joor-language.adoc 
b/components/camel-joor/src/main/docs/joor-language.adoc
index 7fccfdd2..7e490dc 100644
--- a/components/camel-joor/src/main/docs/joor-language.adoc
+++ b/components/camel-joor/src/main/docs/joor-language.adoc
@@ -47,6 +47,111 @@ The jOOR language allows the following variables to be used 
in the script:
 | body | Object | The message body
 |===
 
+== Functions
+
+The jOOR language allows the following functions to be used in the script:
+
+[width="100%",cols="2,1m,7",options="header"]
+|===
+| Function | Description
+| bodyAs(type) | To convert the body to the given type
+| headerAs(name, type) | To convert the header with the name to the given type
+| exchangePropertyAs(name, type) | To convert the exchange property with the 
name to the given type
+|===
+
+These functions are convenient for getting the message body, header or 
exchange properties as a specific Java type.
+
+Here we want to get the message body as a `com.foo.MyUser` type we can do as 
follows:
+[source,java]
+----
+var user = bodyAs(com.foo.MyUser.class);
+----
+
+You can omit _.class_ to make the function a little bit smaller:
+[source,java]
+----
+var user = bodyAs(com.foo.MyUser);
+----
+
+The type must a fully qualified class type, but that can be inconvenient to 
type all the time, so you can configure in
+the `camel-joor.properties` file an import for the type such as:
+
+[source,properties]
+----
+import com.foo.MyUser;
+----
+
+And then the function can be shortened:
+[source,java]
+----
+var user = bodyAs(MyUser);
+----
+
+== Auto imports
+
+The jOOR language will automatic import from:
+
+[source,java]
+----
+import java.util.*;
+import java.util.concurrent.*;
+import org.apache.camel.*;
+import org.apache.camel.util.*;
+----
+
+== Configuration file
+
+You can configure the jOOR language in the `camel-joor.properties` file which 
by default is loaded from the root classpath.
+You can specify a different location with the `configResource` option on the 
jOOR language.
+
+For example you can add additional imports in the `camel-joor.properties` file 
by adding:
+
+[source,properties]
+----
+import com.foo.MyUser;
+import com.bar.*;
+import static com.foo.MyHelper.*;
+----
+
+You can also add aliases (key=value) where an alias will be used as a 
shorthand replacement in the code.
+
+[source,properties]
+----
+echo()=bodyAs(String) + bodyAs(String)
+----
+
+Which allows to use _echo()_ in the jOOR language script such as:
+
+[source,java]
+----
+from("direct:hello")
+    .transform(joor("'Hello ' + echo()"))
+    .log("You said ${body}");
+----
+
+The _echo()_ alias will be replaced with its value resulting in a script as:
+
+[source,java]
+----
+    .transform(joor("'Hello ' + bodyAs(String) + bodyAs(String)"))
+----
+
+You can configure a custom configuration location for the 
`camel-joor.properties` file or reference to a bean in the registry:
+
+[source,java]
+----
+JoorLanguage joor = (JoorLanguage) context.resolveLanguage("joor");
+joor.setConfigResource("ref:MyJoorConfig");
+----
+
+And then register a bean in the registry with id `MyJoorConfig` that is a 
String value with the content.
+
+[source,java]
+----
+String config = "....";
+camelContext.getRegistry().put("MyJoorConfig", config);
+----
+
 == Sample
 
 For example to transform the message using jOOR language to upper case
diff --git 
a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompiler.java
 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompiler.java
index da5bc99..c13ba31 100644
--- 
a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompiler.java
+++ 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorCompiler.java
@@ -17,7 +17,11 @@
 package org.apache.camel.language.joor;
 
 import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
 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.Exchange;
@@ -26,17 +30,47 @@ import org.apache.camel.StaticService;
 import org.apache.camel.support.ScriptHelper;
 import org.apache.camel.support.service.ServiceSupport;
 import org.apache.camel.util.StopWatch;
+import org.apache.camel.util.StringHelper;
 import org.joor.Reflect;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class JoorCompiler extends ServiceSupport implements StaticService {
 
+    private static final Pattern BODY_AS_PATTERN = 
Pattern.compile("bodyAs\\(([A-Za-z0-9.$]*)(.class)\\)");
+    private static final Pattern BODY_AS_PATTERN_NO_CLASS = 
Pattern.compile("bodyAs\\(([A-Za-z0-9.$]*)\\)");
+    private static final Pattern HEADER_AS_PATTERN
+            = 
Pattern.compile("headerAs\\((['|\"][A-Za-z0-9.$]*['|\"]\\s*,\\s*[A-Za-z0-9.$]*)(.class)\\)");
+    private static final Pattern HEADER_AS_PATTERN_NO_CLASS
+            = 
Pattern.compile("headerAs\\((['|\"][A-Za-z0-9.$]*['|\"]\\s*,\\s*[A-Za-z0-9.$]*)\\)");
+    private static final Pattern EXCHANGE_PROPERTY_AS_PATTERN
+            = 
Pattern.compile("exchangePropertyAs\\((['|\"][A-Za-z0-9.$]*['|\"]\\s*,\\s*[A-Za-z0-9.$]*)(.class)\\)");
+    private static final Pattern EXCHANGE_PROPERTY_AS_PATTERN_NO_CLASS
+            = 
Pattern.compile("exchangePropertyAs\\((['|\"][A-Za-z0-9.$]*['|\"]\\s*,\\s*[A-Za-z0-9.$]*)\\)");
+
     private static final Logger LOG = 
LoggerFactory.getLogger(JoorCompiler.class);
     private static final AtomicInteger UUID = new AtomicInteger();
+    private Set<String> imports;
+    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();
@@ -78,7 +112,21 @@ public class JoorCompiler extends ServiceSupport implements 
StaticService {
         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("\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");
+        }
         sb.append("\n");
         sb.append("public class ").append(name).append(" {\n");
         sb.append("\n");
@@ -88,6 +136,10 @@ public class JoorCompiler extends ServiceSupport implements 
StaticService {
         if (!script.contains("return ")) {
             sb.append("return ");
         }
+
+        script = staticHelper(script);
+        script = alias(script);
+
         if (singleQuotes) {
             // single quotes instead of double quotes, as its very annoying 
for string in strings
             String quoted = script.replace('\'', '"');
@@ -106,6 +158,33 @@ public class JoorCompiler extends ServiceSupport 
implements StaticService {
         return sb.toString();
     }
 
+    private String staticHelper(String script) {
+        Matcher matcher = BODY_AS_PATTERN.matcher(script);
+        script = matcher.replaceAll("bodyAs(exchange, $1$2)");
+        matcher = BODY_AS_PATTERN_NO_CLASS.matcher(script);
+        script = matcher.replaceAll("bodyAs(exchange, $1.class)");
+
+        matcher = HEADER_AS_PATTERN.matcher(script);
+        script = matcher.replaceAll("headerAs(exchange, $1$2)");
+        matcher = HEADER_AS_PATTERN_NO_CLASS.matcher(script);
+        script = matcher.replaceAll("headerAs(exchange, $1.class)");
+
+        matcher = EXCHANGE_PROPERTY_AS_PATTERN.matcher(script);
+        script = matcher.replaceAll("exchangePropertyAs(exchange, $1$2)");
+        matcher = EXCHANGE_PROPERTY_AS_PATTERN_NO_CLASS.matcher(script);
+        script = matcher.replaceAll("exchangePropertyAs(exchange, $1.class)");
+        return script;
+    }
+
+    private String alias(String script) {
+        for (Map.Entry<String, String> entry : aliases.entrySet()) {
+            String key = entry.getKey();
+            String value = entry.getValue();
+            script = StringHelper.replaceAll(script, key, value);
+        }
+        return script;
+    }
+
     private static String nextFQN() {
         return "org.apache.camel.language.joor.compiled.JoorLanguage" + 
UUID.incrementAndGet();
     }
diff --git 
a/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorHelper.java
 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorHelper.java
new file mode 100644
index 0000000..950431a
--- /dev/null
+++ 
b/components/camel-joor/src/main/java/org/apache/camel/language/joor/JoorHelper.java
@@ -0,0 +1,41 @@
+/*
+ * 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.Exchange;
+
+/**
+ * A set of helper as static imports for the Camel jOOR language.
+ */
+public final class JoorHelper {
+
+    private JoorHelper() {
+    }
+
+    public static <T> T bodyAs(Exchange exchange, Class<T> type) {
+        return exchange.getMessage().getBody(type);
+    }
+
+    public static <T> T headerAs(Exchange exchange, String name, Class<T> 
type) {
+        return exchange.getMessage().getHeader(name, type);
+    }
+
+    public static <T> T exchangePropertyAs(Exchange exchange, String name, 
Class<T> type) {
+        return exchange.getProperty(name, type);
+    }
+
+}
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 e658cce..9d5b883 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
@@ -16,24 +16,48 @@
  */
 package org.apache.camel.language.joor;
 
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
 import org.apache.camel.Expression;
 import org.apache.camel.Predicate;
 import org.apache.camel.StaticService;
 import org.apache.camel.spi.annotations.Language;
 import org.apache.camel.support.ExpressionToPredicateAdapter;
 import org.apache.camel.support.LanguageSupport;
+import org.apache.camel.support.ScriptHelper;
 import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.StringHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Language("joor")
 public class JoorLanguage extends LanguageSupport implements StaticService {
 
+    private static final Logger LOG = 
LoggerFactory.getLogger(JoorLanguage.class);
+
     private static Boolean java8;
     private final JoorCompiler compiler = new JoorCompiler();
+    private final Set<String> imports = new TreeSet<>();
+    private final Map<String, String> aliases = new HashMap<>();
 
+    private String configResource = 
"classpath:camel-joor.properties?optional=true";
     private boolean preCompile = true;
     private Class<?> resultType;
     private boolean singleQuotes = true;
 
+    public String getConfigResource() {
+        return configResource;
+    }
+
+    public void setConfigResource(String configResource) {
+        this.configResource = configResource;
+        // trigger configuration to be re-loaded
+        loadConfiguration();
+    }
+
     public boolean isPreCompile() {
         return preCompile;
     }
@@ -90,13 +114,20 @@ public class JoorLanguage extends LanguageSupport 
implements StaticService {
     }
 
     @Override
-    public void start() {
+    public void init() {
         if (java8 == null) {
             java8 = getJavaMajorVersion() == 8;
             if (java8) {
                 throw new UnsupportedOperationException("Java 8 is not 
supported. Use Java 11 or higher");
             }
         }
+
+        // attempt to load optional configuration from classpath
+        loadConfiguration();
+    }
+
+    @Override
+    public void start() {
         ServiceHelper.startService(compiler);
     }
 
@@ -105,6 +136,56 @@ public class JoorLanguage extends LanguageSupport 
implements StaticService {
         ServiceHelper.stopService(compiler);
     }
 
+    private void loadConfiguration() {
+        // attempt to load configuration
+        String loaded = 
ScriptHelper.resolveOptionalExternalScript(getCamelContext(), "resource:" + 
configResource);
+        int counter1 = 0;
+        int counter2 = 0;
+        if (loaded != null) {
+            String[] lines = loaded.split("\n");
+            for (String line : lines) {
+                line = line.trim();
+                // skip comments
+                if (line.startsWith("#")) {
+                    continue;
+                }
+                // imports
+                if (line.startsWith("import ")) {
+                    imports.add(line);
+                    counter1++;
+                    continue;
+                }
+                // aliases as key=value
+                String key = StringHelper.before(line, "=");
+                String value = StringHelper.after(line, "=");
+                if (key != null) {
+                    key = key.trim();
+                }
+                if (value != null) {
+                    value = value.trim();
+                }
+                if (key != null && value != null) {
+                    this.aliases.put(key, value);
+                    counter2++;
+                }
+            }
+        }
+        if (counter1 > 0 || counter2 > 0) {
+            LOG.info("Loaded jOOR language imports: {} and aliases: {} from 
configuration: {}", counter1, counter2,
+                    configResource);
+        }
+        if (compiler.getAliases() == null) {
+            compiler.setAliases(aliases);
+        } else {
+            compiler.getAliases().putAll(aliases);
+        }
+        if (compiler.getImports() == null) {
+            compiler.setImports(imports);
+        } else {
+            compiler.getImports().addAll(imports);
+        }
+    }
+
     private static int getJavaMajorVersion() {
         String javaSpecVersion = 
System.getProperty("java.specification.version");
         return javaSpecVersion.contains(".")
diff --git 
a/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorCustomAliasTest.java
 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorCustomAliasTest.java
new file mode 100644
index 0000000..e309fc9
--- /dev/null
+++ 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorCustomAliasTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class JoorCustomAliasTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        context.getRegistry().bind("MyConfig", ""
+                                               + 
"user=org.apache.camel.language.joor.MyUser\n"
+                                               + "import static 
org.apache.camel.language.joor.JoorCustomImportsTest.echo;");
+
+        JoorLanguage joor = (JoorLanguage) context.resolveLanguage("joor");
+        joor.setConfigResource("ref:MyConfig");
+        return context;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        // must return null from script as its a void 
operator, but the joor language expects a returned value
+                        .script(joor(
+                                "var u = new user(); u.setName('Tony'); 
u.setAge(22); exchange.getMessage().setBody(u); return null;"))
+                        .transform().joor("var u = bodyAs(user); return 
echo(u.getName());")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testAlias() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("TonyTony");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public static String echo(String s) {
+        return s + s;
+    }
+
+}
diff --git 
a/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorCustomImportsTest.java
 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorCustomImportsTest.java
new file mode 100644
index 0000000..bb98f71
--- /dev/null
+++ 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorCustomImportsTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+public class JoorCustomImportsTest extends CamelTestSupport {
+
+    @Override
+    protected CamelContext createCamelContext() throws Exception {
+        CamelContext context = super.createCamelContext();
+
+        context.getRegistry().bind("MyConfig", ""
+                                               + "import 
org.apache.camel.language.joor.MyUser;\n"
+                                               + "import static 
org.apache.camel.language.joor.JoorCustomImportsTest.echo;");
+
+        JoorLanguage joor = (JoorLanguage) context.resolveLanguage("joor");
+        joor.setConfigResource("ref:MyConfig");
+        return context;
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                        // must return null from script as its a void 
operator, but the joor language expects a returned value
+                        .script(joor(
+                                "var u = new MyUser(); u.setName('Tony'); 
u.setAge(22); exchange.getMessage().setBody(u); return null;"))
+                        .transform().joor("var u = bodyAs(MyUser.class); 
return echo(u.getName());")
+                        .to("mock:result");
+            }
+        };
+    }
+
+    @Test
+    public void testImport() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("TonyTony");
+
+        template.sendBody("direct:start", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    public static String echo(String s) {
+        return s + s;
+    }
+
+}
diff --git 
a/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorLanguageTest.java
 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorLanguageTest.java
index de582c4..d47a80a 100644
--- 
a/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorLanguageTest.java
+++ 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/JoorLanguageTest.java
@@ -70,6 +70,7 @@ public class JoorLanguageTest extends LanguageTestSupport {
         exchange.getIn().setBody("Hello big world how are you");
 
         assertExpression("message.getBody(String.class).toUpperCase()", "HELLO 
BIG WORLD HOW ARE YOU");
+        assertExpression("bodyAs(String).toLowerCase()", "hello big world how 
are you");
     }
 
     @Test
@@ -88,4 +89,59 @@ public class JoorLanguageTest extends LanguageTestSupport {
                 "No user exists");
     }
 
+    @Test
+    public void testExchangeBodyAs() throws Exception {
+        exchange.getIn().setBody("22");
+
+        assertExpression("2 * bodyAs(int.class)", "44");
+        assertExpression("2 * bodyAs(int)", "44");
+        assertExpression("3 * bodyAs(Integer.class)", "66");
+        assertExpression("3 * bodyAs(Integer)", "66");
+        assertExpression("3 * bodyAs(java.lang.Integer.class)", "66");
+        assertExpression("3 * bodyAs(java.lang.Integer)", "66");
+        assertExpression("var num = bodyAs(int); return num * 4", "88");
+    }
+
+    @Test
+    public void testExchangeHeaderAs() throws Exception {
+        exchange.getIn().setHeader("foo", 22);
+
+        assertExpression("2 * headerAs('foo', int.class)", "44");
+        assertExpression("2 * headerAs('foo', int)", "44");
+        assertExpression("3 * headerAs('foo', Integer.class)", "66");
+        assertExpression("3 * headerAs('foo', Integer)", "66");
+        assertExpression("3 * headerAs('foo', java.lang.Integer.class)", "66");
+        assertExpression("3 * headerAs('foo', java.lang.Integer)", "66");
+        assertExpression("var num = headerAs('foo', int); return num * 4", 
"88");
+
+        assertExpression("2 * headerAs(\"foo\", int.class)", "44");
+        assertExpression("2 * headerAs(\"foo\", int)", "44");
+        assertExpression("3 * headerAs(\"foo\", Integer.class)", "66");
+        assertExpression("3 * headerAs(\"foo\", Integer)", "66");
+        assertExpression("3 * headerAs(\"foo\", java.lang.Integer.class)", 
"66");
+        assertExpression("3 * headerAs(\"foo\", java.lang.Integer)", "66");
+        assertExpression("var num = headerAs(\"foo\", int); return num * 4", 
"88");
+    }
+
+    @Test
+    public void testExchangePropertyAs() throws Exception {
+        exchange.setProperty("bar", 22);
+
+        assertExpression("2 * exchangePropertyAs('bar', int.class)", "44");
+        assertExpression("2 * exchangePropertyAs('bar', int)", "44");
+        assertExpression("3 * exchangePropertyAs('bar', Integer.class)", "66");
+        assertExpression("3 * exchangePropertyAs('bar', Integer)", "66");
+        assertExpression("3 * exchangePropertyAs('bar', 
java.lang.Integer.class)", "66");
+        assertExpression("3 * exchangePropertyAs('bar', java.lang.Integer)", 
"66");
+        assertExpression("var num = exchangePropertyAs('bar', int); return num 
* 4", "88");
+
+        assertExpression("2 * exchangePropertyAs(\"bar\", int.class)", "44");
+        assertExpression("2 * exchangePropertyAs(\"bar\", int)", "44");
+        assertExpression("3 * exchangePropertyAs(\"bar\", Integer.class)", 
"66");
+        assertExpression("3 * exchangePropertyAs(\"bar\", Integer)", "66");
+        assertExpression("3 * exchangePropertyAs(\"bar\", 
java.lang.Integer.class)", "66");
+        assertExpression("3 * exchangePropertyAs(\"bar\", java.lang.Integer)", 
"66");
+        assertExpression("var num = exchangePropertyAs(\"bar\", int); return 
num * 4", "88");
+    }
+
 }
diff --git 
a/components/camel-joor/src/test/java/org/apache/camel/language/joor/MyUser.java
 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/MyUser.java
new file mode 100644
index 0000000..5aabfbc
--- /dev/null
+++ 
b/components/camel-joor/src/test/java/org/apache/camel/language/joor/MyUser.java
@@ -0,0 +1,39 @@
+/*
+ * 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 MyUser {
+
+    private String name;
+    private int age;
+
+    public String getName() {
+        return name;
+    }
+
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    public int getAge() {
+        return age;
+    }
+
+    public void setAge(int age) {
+        this.age = age;
+    }
+}
diff --git 
a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
 
b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
index fe540e8..3a4352b 100644
--- 
a/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
+++ 
b/core/camel-base/src/main/java/org/apache/camel/impl/engine/AbstractCamelContext.java
@@ -1656,7 +1656,11 @@ public abstract class AbstractCamelContext extends 
BaseService
                 if (language != null) {
                     if (language instanceof Service) {
                         try {
-                            startService((Service) language);
+                            Service service = (Service) language;
+                            // init service first
+                            CamelContextAware.trySetCamelContext(service, 
camelContext);
+                            service.init();
+                            startService(service);
                         } catch (Exception e) {
                             throw 
RuntimeCamelException.wrapRuntimeCamelException(e);
                         }
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/ResourceHelper.java 
b/core/camel-support/src/main/java/org/apache/camel/support/ResourceHelper.java
index 916775f..76a4aaa 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/ResourceHelper.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/ResourceHelper.java
@@ -152,6 +152,48 @@ public final class ResourceHelper {
      * <p/>
      * If possible recommended to use {@link 
#resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)}
      *
+     * @param  camelContext        the camel context
+     * @param  uri                 URI of the resource
+     * @return                     the resource as an {@link InputStream}. 
Remember to close this stream after usage. Or
+     *                             <tt>null</tt> if not found.
+     * @throws java.io.IOException is thrown if error loading the resource
+     */
+    public static InputStream resolveResourceAsInputStream(CamelContext 
camelContext, String uri) throws IOException {
+        if (uri.startsWith("ref:")) {
+            String ref = uri.substring(4);
+            String value = CamelContextHelper.mandatoryLookup(camelContext, 
ref, String.class);
+            return new ByteArrayInputStream(value.getBytes());
+        } else if (uri.startsWith("bean:")) {
+            String bean = uri.substring(5);
+            Exchange dummy = new DefaultExchange(camelContext);
+            Object out = 
camelContext.resolveLanguage("bean").createExpression(bean).evaluate(dummy, 
Object.class);
+            if (dummy.getException() != null) {
+                IOException io = new IOException("Cannot find resource: " + 
uri + " from calling the bean");
+                io.initCause(dummy.getException());
+                throw io;
+            }
+            if (out != null) {
+                InputStream is = 
camelContext.getTypeConverter().tryConvertTo(InputStream.class, dummy, out);
+                if (is == null) {
+                    String text = 
camelContext.getTypeConverter().tryConvertTo(String.class, dummy, out);
+                    if (text != null) {
+                        return new ByteArrayInputStream(text.getBytes());
+                    }
+                } else {
+                    return is;
+                }
+            } else {
+                throw new IOException("Cannot find resource: " + uri + " from 
calling the bean");
+            }
+        }
+        return resolveResourceAsInputStream(camelContext.getClassResolver(), 
uri);
+    }
+
+    /**
+     * Resolves the resource.
+     * <p/>
+     * If possible recommended to use {@link 
#resolveMandatoryResourceAsUrl(org.apache.camel.spi.ClassResolver, String)}
+     *
      * @param  classResolver       the class resolver to load the resource 
from the classpath
      * @param  uri                 URI of the resource
      * @return                     the resource as an {@link InputStream}. 
Remember to close this stream after usage. Or
diff --git 
a/core/camel-support/src/main/java/org/apache/camel/support/ScriptHelper.java 
b/core/camel-support/src/main/java/org/apache/camel/support/ScriptHelper.java
index c586356..a9f2ee8 100644
--- 
a/core/camel-support/src/main/java/org/apache/camel/support/ScriptHelper.java
+++ 
b/core/camel-support/src/main/java/org/apache/camel/support/ScriptHelper.java
@@ -37,6 +37,10 @@ public final class ScriptHelper {
      * <tt>resource:file:/var/myscript.groovy</tt>.
      * <p/>
      * If not then the returned value is returned as-is.
+     * <p/>
+     * The resource is regarded as mandatory and an exception is thrown if the 
resource cannot be loaded. Adding
+     * </tt>?optional=true<tt> as suffix will mark the resource as optional 
and <tt>null</tt> is returned if the
+     * resource could not be loaded.
      */
     public static String resolveOptionalExternalScript(CamelContext 
camelContext, String expression) {
         return resolveOptionalExternalScript(camelContext, null, expression);
@@ -51,6 +55,10 @@ public final class ScriptHelper {
      * <p/>
      * If the exchange is provided (not null), then the external script can be 
referred via simple language for dynamic
      * values, etc. <tt>resource:classpath:${header.myFileName}</tt>
+     * <p/>
+     * The resource is regarded as mandatory and an exception is thrown if the 
resource cannot be loaded. Adding
+     * </tt>?optional=true<tt> as suffix will mark the resource as optional 
and <tt>null</tt> is returned if the
+     * resource could not be loaded.
      */
     public static String resolveOptionalExternalScript(CamelContext 
camelContext, Exchange exchange, String expression) {
         if (expression == null) {
@@ -66,34 +74,52 @@ public final class ScriptHelper {
         }
 
         // must start with resource: to denote an external resource
-        if (external.startsWith("resource:")) {
+        if (hasExternalScript(external)) {
+            // clip resource: prefix
             external = external.substring(9);
 
-            if (ResourceHelper.hasScheme(external)) {
-                if (exchange != null && 
LanguageSupport.hasSimpleFunction(external)) {
-                    Language simple = 
exchange.getContext().resolveLanguage("simple");
-                    external = 
simple.createExpression(external).evaluate(exchange, String.class);
-                }
+            boolean optional = external.endsWith("?optional=true");
+            if (optional) {
+                external = external.substring(0, external.length() - 14);
+            }
 
-                InputStream is = null;
-                try {
+            if (exchange != null && 
LanguageSupport.hasSimpleFunction(external)) {
+                Language simple = 
exchange.getContext().resolveLanguage("simple");
+                external = 
simple.createExpression(external).evaluate(exchange, String.class);
+            }
+
+            InputStream is = null;
+            try {
+                if (optional) {
+                    is = 
ResourceHelper.resolveResourceAsInputStream(camelContext, external);
+                } else {
                     is = 
ResourceHelper.resolveMandatoryResourceAsInputStream(camelContext, external);
+                }
+                if (is != null) {
                     expression = 
camelContext.getTypeConverter().convertTo(String.class, is);
-                } catch (IOException e) {
-                    throw new RuntimeCamelException("Cannot load resource " + 
external, e);
-                } finally {
-                    IOHelper.close(is);
+                } else {
+                    expression = null;
                 }
+            } catch (IOException e) {
+                throw new RuntimeCamelException("Cannot load resource " + 
external, e);
+            } finally {
+                IOHelper.close(is);
             }
         }
 
         return expression;
     }
 
+    /**
+     * Whether the expression/predicate refers to an external script on the 
file/classpath etc. This requires to use the
+     * prefix <tt>resource:</tt> such as 
<tt>resource:classpath:com/foo/myscript.groovy</tt>,
+     * <tt>resource:ref:myResource</tt>, 
<tt>resource:file:/var/myscript.groovy</tt>.
+     */
     public static boolean hasExternalScript(String external) {
         if (external.startsWith("resource:")) {
             external = external.substring(9);
-            if (ResourceHelper.hasScheme(external) && 
LanguageSupport.hasSimpleFunction(external)) {
+            // ref and bean is also supported
+            if (ResourceHelper.hasScheme(external) || 
external.startsWith("ref:") || external.startsWith("bean:")) {
                 return true;
             }
         }
diff --git a/docs/components/modules/languages/pages/joor-language.adoc 
b/docs/components/modules/languages/pages/joor-language.adoc
index 146b383..c0de6ab 100644
--- a/docs/components/modules/languages/pages/joor-language.adoc
+++ b/docs/components/modules/languages/pages/joor-language.adoc
@@ -49,6 +49,111 @@ The jOOR language allows the following variables to be used 
in the script:
 | body | Object | The message body
 |===
 
+== Functions
+
+The jOOR language allows the following functions to be used in the script:
+
+[width="100%",cols="2,1m,7",options="header"]
+|===
+| Function | Description
+| bodyAs(type) | To convert the body to the given type
+| headerAs(name, type) | To convert the header with the name to the given type
+| exchangePropertyAs(name, type) | To convert the exchange property with the 
name to the given type
+|===
+
+These functions are convenient for getting the message body, header or 
exchange properties as a specific Java type.
+
+Here we want to get the message body as a `com.foo.MyUser` type we can do as 
follows:
+[source,java]
+----
+var user = bodyAs(com.foo.MyUser.class);
+----
+
+You can omit _.class_ to make the function a little bit smaller:
+[source,java]
+----
+var user = bodyAs(com.foo.MyUser);
+----
+
+The type must a fully qualified class type, but that can be inconvenient to 
type all the time, so you can configure in
+the `camel-joor.properties` file an import for the type such as:
+
+[source,properties]
+----
+import com.foo.MyUser;
+----
+
+And then the function can be shortened:
+[source,java]
+----
+var user = bodyAs(MyUser);
+----
+
+== Auto imports
+
+The jOOR language will automatic import from:
+
+[source,java]
+----
+import java.util.*;
+import java.util.concurrent.*;
+import org.apache.camel.*;
+import org.apache.camel.util.*;
+----
+
+== Configuration file
+
+You can configure the jOOR language in the `camel-joor.properties` file which 
by default is loaded from the root classpath.
+You can specify a different location with the `configResource` option on the 
jOOR language.
+
+For example you can add additional imports in the `camel-joor.properties` file 
by adding:
+
+[source,properties]
+----
+import com.foo.MyUser;
+import com.bar.*;
+import static com.foo.MyHelper.*;
+----
+
+You can also add aliases (key=value) where an alias will be used as a 
shorthand replacement in the code.
+
+[source,properties]
+----
+echo()=bodyAs(String) + bodyAs(String)
+----
+
+Which allows to use _echo()_ in the jOOR language script such as:
+
+[source,java]
+----
+from("direct:hello")
+    .transform(joor("'Hello ' + echo()"))
+    .log("You said ${body}");
+----
+
+The _echo()_ alias will be replaced with its value resulting in a script as:
+
+[source,java]
+----
+    .transform(joor("'Hello ' + bodyAs(String) + bodyAs(String)"))
+----
+
+You can configure a custom configuration location for the 
`camel-joor.properties` file or reference to a bean in the registry:
+
+[source,java]
+----
+JoorLanguage joor = (JoorLanguage) context.resolveLanguage("joor");
+joor.setConfigResource("ref:MyJoorConfig");
+----
+
+And then register a bean in the registry with id `MyJoorConfig` that is a 
String value with the content.
+
+[source,java]
+----
+String config = "....";
+camelContext.getRegistry().put("MyJoorConfig", config);
+----
+
 == Sample
 
 For example to transform the message using jOOR language to upper case

Reply via email to