Added support for configurable java action method name
Implement support for fully qualified method names
Added test cases for fully qualified method names
diff --git a/core/javaAction/proxy/src/main/java/openwhisk/java/action/JarLoader.java b/core/javaAction/proxy/src/main/java/openwhisk/java/action/JarLoader.java
index 13bf617..76de8f0 100644
--- a/core/javaAction/proxy/src/main/java/openwhisk/java/action/JarLoader.java
+++ b/core/javaAction/proxy/src/main/java/openwhisk/java/action/JarLoader.java
@@ -52,13 +52,17 @@
return destinationPath;
}
- public JarLoader(Path jarPath, String mainClassName)
+ public JarLoader(Path jarPath, String entrypoint)
throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, SecurityException {
super(new URL[] { jarPath.toUri().toURL() });
- this.mainClass = loadClass(mainClassName);
+ final String[] splittedEntrypoint = entrypoint.split("#");
+ final String entrypointClassName = splittedEntrypoint[0];
+ final String entrypointMethodName = splittedEntrypoint.length > 1 ? splittedEntrypoint[1] : "main";
- Method m = mainClass.getMethod("main", new Class[] { JsonObject.class });
+ this.mainClass = loadClass(entrypointClassName);
+
+ Method m = mainClass.getMethod(entrypointMethodName, new Class[] { JsonObject.class });
m.setAccessible(true);
int modifiers = m.getModifiers();
if (m.getReturnType() != JsonObject.class || !Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
diff --git a/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala b/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
index 297a8f6..86e97d3 100644
--- a/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
+++ b/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package runtime.actionContainers
+package actionContainers
import org.junit.runner.RunWith
import org.scalatest.FlatSpec
@@ -55,24 +55,25 @@
withJavaContainer(
{ c =>
val jar = JarBuilder.mkBase64Jar(
- Seq("example", "HelloWhisk.java") -> """
- | package example;
- |
- | import com.google.gson.JsonObject;
- |
- | public class HelloWhisk {
- | public static JsonObject main(JsonObject args) {
- | JsonObject response = new JsonObject();
- | response.addProperty("api_host", System.getenv("__OW_API_HOST"));
- | response.addProperty("api_key", System.getenv("__OW_API_KEY"));
- | response.addProperty("namespace", System.getenv("__OW_NAMESPACE"));
- | response.addProperty("action_name", System.getenv("__OW_ACTION_NAME"));
- | response.addProperty("activation_id", System.getenv("__OW_ACTIVATION_ID"));
- | response.addProperty("deadline", System.getenv("__OW_DEADLINE"));
- | return response;
- | }
- | }
- """.stripMargin.trim)
+ Seq("example", "HelloWhisk.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.JsonObject;
+ |
+ | public class HelloWhisk {
+ | public static JsonObject main(JsonObject args) {
+ | JsonObject response = new JsonObject();
+ | response.addProperty("api_host", System.getenv("__OW_API_HOST"));
+ | response.addProperty("api_key", System.getenv("__OW_API_KEY"));
+ | response.addProperty("namespace", System.getenv("__OW_NAMESPACE"));
+ | response.addProperty("action_name", System.getenv("__OW_ACTION_NAME"));
+ | response.addProperty("activation_id", System.getenv("__OW_ACTIVATION_ID"));
+ | response.addProperty("deadline", System.getenv("__OW_DEADLINE"));
+ | return response;
+ | }
+ | }
+ """.stripMargin.trim)
val (initCode, _) = c.init(initPayload("example.HelloWhisk", jar))
initCode should be(200)
@@ -93,20 +94,21 @@
it should "support valid flows" in {
val (out, err) = withJavaContainer { c =>
val jar = JarBuilder.mkBase64Jar(
- Seq("example", "HelloWhisk.java") -> """
- | package example;
- |
- | import com.google.gson.JsonObject;
- |
- | public class HelloWhisk {
- | public static JsonObject main(JsonObject args) {
- | String name = args.getAsJsonPrimitive("name").getAsString();
- | JsonObject response = new JsonObject();
- | response.addProperty("greeting", "Hello " + name + "!");
- | return response;
- | }
- | }
- """.stripMargin.trim)
+ Seq("example", "HelloWhisk.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.JsonObject;
+ |
+ | public class HelloWhisk {
+ | public static JsonObject main(JsonObject args) {
+ | String name = args.getAsJsonPrimitive("name").getAsString();
+ | JsonObject response = new JsonObject();
+ | response.addProperty("greeting", "Hello " + name + "!");
+ | return response;
+ | }
+ | }
+ """.stripMargin.trim)
val (initCode, _) = c.init(initPayload("example.HelloWhisk", jar))
initCode should be(200)
@@ -124,25 +126,57 @@
err.trim shouldBe empty
}
+ it should "support valid actions with non 'main' names" in {
+ val (out, err) = withJavaContainer { c =>
+ val jar = JarBuilder.mkBase64Jar(
+ Seq("example", "HelloWhisk.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.JsonObject;
+ |
+ | public class HelloWhisk {
+ | public static JsonObject hello(JsonObject args) {
+ | String name = args.getAsJsonPrimitive("name").getAsString();
+ | JsonObject response = new JsonObject();
+ | response.addProperty("greeting", "Hello " + name + "!");
+ | return response;
+ | }
+ | }
+ """.stripMargin.trim)
+
+ val (initCode, _) = c.init(initPayload("example.HelloWhisk#hello", jar))
+ initCode should be(200)
+
+ val (runCode, out) = c.run(runPayload(JsObject("name" -> JsString("Whisk"))))
+ runCode should be(200)
+ out should be(Some(JsObject("greeting" -> JsString("Hello Whisk!"))))
+ }
+
+ out.trim shouldBe empty
+ err.trim shouldBe empty
+ }
+
it should "handle unicode in source, input params, logs, and result" in {
val (out, err) = withJavaContainer { c =>
val jar = JarBuilder.mkBase64Jar(
- Seq("example", "HelloWhisk.java") -> """
- | package example;
- |
- | import com.google.gson.JsonObject;
- |
- | public class HelloWhisk {
- | public static JsonObject main(JsonObject args) {
- | String delimiter = args.getAsJsonPrimitive("delimiter").getAsString();
- | JsonObject response = new JsonObject();
- | String str = delimiter + " ☃ " + delimiter;
- | System.out.println(str);
- | response.addProperty("winter", str);
- | return response;
- | }
- | }
- """.stripMargin)
+ Seq("example", "HelloWhisk.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.JsonObject;
+ |
+ | public class HelloWhisk {
+ | public static JsonObject main(JsonObject args) {
+ | String delimiter = args.getAsJsonPrimitive("delimiter").getAsString();
+ | JsonObject response = new JsonObject();
+ | String str = delimiter + " ☃ " + delimiter;
+ | System.out.println(str);
+ | response.addProperty("winter", str);
+ | return response;
+ | }
+ | }
+ """.stripMargin)
val (initCode, _) = c.init(initPayload("example.HelloWhisk", jar))
val (runCode, runRes) = c.run(runPayload(JsObject("delimiter" -> JsString("❄"))))
@@ -175,17 +209,18 @@
it should "return some error on action error" in {
val (out, err) = withJavaContainer { c =>
val jar = JarBuilder.mkBase64Jar(
- Seq("example", "HelloWhisk.java") -> """
- | package example;
- |
- | import com.google.gson.JsonObject;
- |
- | public class HelloWhisk {
- | public static JsonObject main(JsonObject args) throws Exception {
- | throw new Exception("noooooooo");
- | }
- | }
- """.stripMargin.trim)
+ Seq("example", "HelloWhisk.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.JsonObject;
+ |
+ | public class HelloWhisk {
+ | public static JsonObject main(JsonObject args) throws Exception {
+ | throw new Exception("noooooooo");
+ | }
+ | }
+ """.stripMargin.trim)
val (initCode, _) = c.init(initPayload("example.HelloWhisk", jar))
initCode should be(200)
@@ -204,19 +239,20 @@
it should "support application errors" in {
val (out, err) = withJavaContainer { c =>
val jar = JarBuilder.mkBase64Jar(
- Seq("example", "Error.java") -> """
- | package example;
- |
- | import com.google.gson.JsonObject;
- |
- | public class Error {
- | public static JsonObject main(JsonObject args) throws Exception {
- | JsonObject error = new JsonObject();
- | error.addProperty("error", "This action is unhappy.");
- | return error;
- | }
- | }
- """.stripMargin.trim)
+ Seq("example", "Error.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.JsonObject;
+ |
+ | public class Error {
+ | public static JsonObject main(JsonObject args) throws Exception {
+ | JsonObject error = new JsonObject();
+ | error.addProperty("error", "This action is unhappy.");
+ | return error;
+ | }
+ | }
+ """.stripMargin.trim)
val (initCode, _) = c.init(initPayload("example.Error", jar))
initCode should be(200)
@@ -234,19 +270,20 @@
it should "survive System.exit" in {
val (out, err) = withJavaContainer { c =>
- val jar =
- JarBuilder.mkBase64Jar(Seq("example", "Quitter.java") -> """
- | package example;
- |
- | import com.google.gson.*;
- |
- | public class Quitter {
- | public static JsonObject main(JsonObject main) {
- | System.exit(1);
- | return new JsonObject();
- | }
- | }
- """.stripMargin.trim)
+ val jar = JarBuilder.mkBase64Jar(
+ Seq("example", "Quitter.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.*;
+ |
+ | public class Quitter {
+ | public static JsonObject main(JsonObject main) {
+ | System.exit(1);
+ | return new JsonObject();
+ | }
+ | }
+ """.stripMargin.trim)
val (initCode, _) = c.init(initPayload("example.Quitter", jar))
initCode should be(200)
@@ -264,18 +301,19 @@
it should "enforce that the user returns an object" in {
withJavaContainer { c =>
- val jar =
- JarBuilder.mkBase64Jar(Seq("example", "Nuller.java") -> """
- | package example;
- |
- | import com.google.gson.*;
- |
- | public class Nuller {
- | public static JsonObject main(JsonObject args) {
- | return null;
- | }
- | }
- """.stripMargin.trim)
+ val jar = JarBuilder.mkBase64Jar(
+ Seq("example", "Nuller.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.*;
+ |
+ | public class Nuller {
+ | public static JsonObject main(JsonObject args) {
+ | return null;
+ | }
+ | }
+ """.stripMargin.trim)
val (initCode, _) = c.init(initPayload("example.Nuller", jar))
initCode should be(200)
@@ -290,43 +328,45 @@
val dynamicLoadingJar = JarBuilder.mkBase64Jar(
Seq(
- Seq("example", "EntryPoint.java") -> """
- | package example;
- |
- | import com.google.gson.*;
- | import java.lang.reflect.*;
- |
- | public class EntryPoint {
- | private final static String CLASS_NAME = "example.DynamicClass";
- | public static JsonObject main(JsonObject args) throws Exception {
- | String cl = args.getAsJsonPrimitive("classLoader").getAsString();
- |
- | Class d = null;
- | if("local".equals(cl)) {
- | d = Class.forName(CLASS_NAME);
- | } else if("thread".equals(cl)) {
- | d = Thread.currentThread().getContextClassLoader().loadClass(CLASS_NAME);
- | }
- |
- | Object o = d.newInstance();
- | Method m = o.getClass().getMethod("getMessage");
- | String msg = (String)m.invoke(o);
- |
- | JsonObject response = new JsonObject();
- | response.addProperty("message", msg);
- | return response;
- | }
- | }
- |""".stripMargin.trim,
- Seq("example", "DynamicClass.java") -> """
- | package example;
- |
- | public class DynamicClass {
- | public String getMessage() {
- | return "dynamic!";
- | }
- | }
- |""".stripMargin.trim))
+ Seq("example", "EntryPoint.java") ->
+ """
+ | package example;
+ |
+ | import com.google.gson.*;
+ | import java.lang.reflect.*;
+ |
+ | public class EntryPoint {
+ | private final static String CLASS_NAME = "example.DynamicClass";
+ | public static JsonObject main(JsonObject args) throws Exception {
+ | String cl = args.getAsJsonPrimitive("classLoader").getAsString();
+ |
+ | Class d = null;
+ | if("local".equals(cl)) {
+ | d = Class.forName(CLASS_NAME);
+ | } else if("thread".equals(cl)) {
+ | d = Thread.currentThread().getContextClassLoader().loadClass(CLASS_NAME);
+ | }
+ |
+ | Object o = d.newInstance();
+ | Method m = o.getClass().getMethod("getMessage");
+ | String msg = (String)m.invoke(o);
+ |
+ | JsonObject response = new JsonObject();
+ | response.addProperty("message", msg);
+ | return response;
+ | }
+ | }
+ """.stripMargin.trim,
+ Seq("example", "DynamicClass.java") ->
+ """
+ | package example;
+ |
+ | public class DynamicClass {
+ | public String getMessage() {
+ | return "dynamic!";
+ | }
+ | }
+ """.stripMargin.trim))
def classLoaderTest(param: String) = {
val (out, err) = withJavaContainer { c =>