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