This is an automated email from the ASF dual-hosted git repository.
dgrove pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwhisk-runtime-java.git
The following commit(s) were added to refs/heads/master by this push:
new e366197 Support array result include sequence action (#140)
e366197 is described below
commit e366197d4c13c409602d3dbea6c9e45190b0b644
Author: ningyougang <[email protected]>
AuthorDate: Mon Aug 15 22:54:46 2022 +0800
Support array result include sequence action (#140)
* Support array result
* Use go1.18 to build actionloop
* Use actionloop 1.20.0
---
README.md | 30 ++++++++++++
.../openwhisk/runtime/java/action/JarLoader.java | 32 ++++++-------
.../openwhisk/runtime/java/action/Proxy.java | 39 ++++++++++++++--
core/java8actionloop/Dockerfile | 8 ++--
core/java8actionloop/lib/src/Launcher.java | 52 ++++++++++++++++-----
.../JavaActionContainerTests.scala | 54 ++++++++++++++++++++--
6 files changed, 178 insertions(+), 37 deletions(-)
diff --git a/README.md b/README.md
index 58f46c9..c7ce6ca 100644
--- a/README.md
+++ b/README.md
@@ -68,6 +68,36 @@ If needed you can also customize the method name of your
Java action. This
can be done by specifying the Java fully-qualified method name of your action,
e.q., `--main com.example.MyMain#methodName`
+Not only support return JsonObject but also support return JsonArray, the main
function would be:
+
+```java
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+public class HelloArray {
+ public static JsonArray main(JsonObject args) {
+ JsonArray jsonArray = new JsonArray();
+ jsonArray.add("a");
+ jsonArray.add("b");
+ return jsonArray;
+ }
+}
+```
+
+And support array result for sequence action as well, the first action's array
result can be used as next action's input parameter.
+
+So the function would be:
+
+```java
+import com.google.gson.JsonArray;
+
+public class Sort {
+ public static JsonArray main(JsonArray args) {
+ return args;
+ }
+}
+```
+
### Create the Java Action
To use as a docker action:
```
diff --git
a/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
b/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
index e17d0a9..fa45197 100644
---
a/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
+++
b/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
@@ -32,11 +32,13 @@ import java.util.Base64;
import java.util.Collections;
import java.util.Map;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class JarLoader extends URLClassLoader {
- private final Class<?> mainClass;
- private final Method mainMethod;
+ public final Class<?> mainClass;
+ public final String entrypointMethodName;
public static Path saveBase64EncodedFile(InputStream encoded) throws
Exception {
Base64.Decoder decoder = Base64.getDecoder();
@@ -58,26 +60,24 @@ public class JarLoader extends URLClassLoader {
final String[] splittedEntrypoint = entrypoint.split("#");
final String entrypointClassName = splittedEntrypoint[0];
- final String entrypointMethodName = splittedEntrypoint.length > 1 ?
splittedEntrypoint[1] : "main";
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)) {
- throw new NoSuchMethodException("main");
+ this.entrypointMethodName = splittedEntrypoint.length > 1 ?
splittedEntrypoint[1] : "main";
+ Method[] methods = mainClass.getDeclaredMethods();
+ Boolean existMain = false;
+ for(Method method: methods) {
+ if (method.getName().equals(this.entrypointMethodName)) {
+ existMain = true;
+ break;
+ }
+ }
+ if (!existMain) {
+ throw new NoSuchMethodException(this.entrypointMethodName);
}
- this.mainMethod = m;
- }
-
- public JsonObject invokeMain(JsonObject arg, Map<String, String> env)
throws Exception {
- augmentEnv(env);
- return (JsonObject) mainMethod.invoke(null, arg);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
- private static void augmentEnv(Map<String, String> newEnv) {
+ public static void augmentEnv(Map<String, String> newEnv) {
try {
for (Class cl : Collections.class.getDeclaredClasses()) {
if
("java.util.Collections$UnmodifiableMap".equals(cl.getName())) {
diff --git
a/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
b/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
index 4c0dc21..b8519ec 100644
---
a/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
+++
b/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
@@ -24,6 +24,8 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
@@ -31,6 +33,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -139,7 +142,17 @@ public class Proxy {
InputStream is = t.getRequestBody();
JsonParser parser = new JsonParser();
JsonObject body = parser.parse(new BufferedReader(new
InputStreamReader(is, StandardCharsets.UTF_8))).getAsJsonObject();
- JsonObject inputObject = body.getAsJsonObject("value");
+ JsonParser json = new JsonParser();
+ JsonObject payloadForJsonObject =
json.parse("{}").getAsJsonObject();
+ JsonArray payloadForJsonArray =
json.parse("[]").getAsJsonArray();
+ Boolean isJsonObjectParam = true;
+ JsonElement inputJsonElement = body.get("value");
+ if (inputJsonElement.isJsonObject()) {
+ payloadForJsonObject = inputJsonElement.getAsJsonObject();
+ } else {
+ payloadForJsonArray = inputJsonElement.getAsJsonArray();
+ isJsonObjectParam = false;
+ }
HashMap<String, String> env = new HashMap<String, String>();
Set<Map.Entry<String, JsonElement>> entrySet = body.entrySet();
@@ -153,8 +166,28 @@ public class Proxy {
Thread.currentThread().setContextClassLoader(loader);
System.setSecurityManager(new WhiskSecurityManager());
- // User code starts running here.
- JsonObject output = loader.invokeMain(inputObject, env);
+ Method mainMethod = null;
+ String mainMethodName = loader.entrypointMethodName;
+ if (isJsonObjectParam) {
+ mainMethod = loader.mainClass.getMethod(mainMethodName,
new Class[] { JsonObject.class });
+ } else {
+ mainMethod = loader.mainClass.getMethod(mainMethodName,
new Class[] { JsonArray.class });
+ }
+ mainMethod.setAccessible(true);
+ int modifiers = mainMethod.getModifiers();
+ if ((mainMethod.getReturnType() != JsonObject.class &&
mainMethod.getReturnType() != JsonArray.class) || !Modifier.isStatic(modifiers)
|| !Modifier.isPublic(modifiers)) {
+ throw new NoSuchMethodException(mainMethodName);
+ }
+
+ // User code starts running here. the return object supports
JsonObject and JsonArray both.
+ Object output;
+ if (isJsonObjectParam) {
+ loader.augmentEnv(env);
+ output = mainMethod.invoke(null, payloadForJsonObject);
+ } else {
+ loader.augmentEnv(env);
+ output = mainMethod.invoke(null, payloadForJsonArray);
+ }
// User code finished running here.
if (output == null) {
diff --git a/core/java8actionloop/Dockerfile b/core/java8actionloop/Dockerfile
index 510393d..e5931a4 100644
--- a/core/java8actionloop/Dockerfile
+++ b/core/java8actionloop/Dockerfile
@@ -16,7 +16,7 @@
#
# build go proxy from source
-FROM golang:1.16 AS builder_source
+FROM golang:1.18 AS builder_source
ARG GO_PROXY_GITHUB_USER=apache
ARG GO_PROXY_GITHUB_BRANCH=master
RUN git clone --branch ${GO_PROXY_GITHUB_BRANCH} \
@@ -25,13 +25,13 @@ RUN git clone --branch ${GO_PROXY_GITHUB_BRANCH} \
mv proxy /bin/proxy
# or build it from a release
-FROM golang:1.16 AS builder_release
-ARG [email protected]
+FROM golang:1.18 AS builder_release
+ARG [email protected]
RUN curl -sL \
https://github.com/apache/openwhisk-runtime-go/archive/{$GO_PROXY_RELEASE_VERSION}.tar.gz\
| tar xzf -\
&& cd openwhisk-runtime-go-*/main\
- && GO111MODULE=on go build -o /bin/proxy
+ && GO111MODULE=on CGO_ENABLED=0 go build -o /bin/proxy
# Use AdoptOpenJDK's JDK8, OpenJ9, ubuntu
FROM ibm-semeru-runtimes:open-8u332-b09-jdk-focal
diff --git a/core/java8actionloop/lib/src/Launcher.java
b/core/java8actionloop/lib/src/Launcher.java
index ef571e9..f0659d2 100644
--- a/core/java8actionloop/lib/src/Launcher.java
+++ b/core/java8actionloop/lib/src/Launcher.java
@@ -59,18 +59,22 @@ class Launcher {
}
mainClass = Class.forName(mainClassName);
- Method m = mainClass.getMethod(mainMethodName, new Class[] {
JsonObject.class });
- m.setAccessible(true);
- int modifiers = m.getModifiers();
- if (m.getReturnType() != JsonObject.class ||
!Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
+ Method[] methods = mainClass.getDeclaredMethods();
+ Boolean existMain = false;
+ for(Method method: methods) {
+ if (method.getName().equals(mainMethodName)) {
+ existMain = true;
+ break;
+ }
+ }
+ if (!existMain) {
throw new NoSuchMethodException(mainMethodName);
}
- mainMethod = m;
}
- private static JsonObject invokeMain(JsonObject arg, Map<String, String>
env) throws Exception {
+ private static Object invokeMain(JsonElement arg, Map<String, String> env)
throws Exception {
augmentEnv(env);
- return (JsonObject) mainMethod.invoke(null, arg);
+ return mainMethod.invoke(null, arg);
}
private static SecurityManager defaultSecurityManager = null;
@@ -119,7 +123,9 @@ class Launcher {
new OutputStreamWriter(
new FileOutputStream("/dev/fd/3"), "UTF-8"));
JsonParser json = new JsonParser();
- JsonObject empty = json.parse("{}").getAsJsonObject();
+ JsonObject emptyForJsonObject = json.parse("{}").getAsJsonObject();
+ JsonArray emptyForJsonArray = json.parse("[]").getAsJsonArray();
+ Boolean isJsonObjectParam = true;
String input = "";
while (true) {
try {
@@ -127,14 +133,19 @@ class Launcher {
if (input == null)
break;
JsonElement element = json.parse(input);
- JsonObject payload = empty.deepCopy();
+ JsonObject payloadForJsonObject =
emptyForJsonObject.deepCopy();
+ JsonArray payloadForJsonArray = emptyForJsonArray.deepCopy();
HashMap<String, String> env = new HashMap<String, String>();
if (element.isJsonObject()) {
// collect payload and environment
for (Map.Entry<String, JsonElement> entry :
element.getAsJsonObject().entrySet()) {
if (entry.getKey().equals("value")) {
if (entry.getValue().isJsonObject())
- payload = entry.getValue().getAsJsonObject();
+ payloadForJsonObject =
entry.getValue().getAsJsonObject();
+ else {
+ payloadForJsonArray =
entry.getValue().getAsJsonArray();
+ isJsonObjectParam = false;
+ }
} else {
env.put(String.format("__OW_%s",
entry.getKey().toUpperCase()),
entry.getValue().getAsString());
@@ -142,7 +153,26 @@ class Launcher {
}
augmentEnv(env);
}
- JsonElement response = invokeMain(payload, env);
+
+ Method m = null;
+ if (isJsonObjectParam) {
+ m = mainClass.getMethod(mainMethodName, new Class[] {
JsonObject.class });
+ } else {
+ m = mainClass.getMethod(mainMethodName, new Class[] {
JsonArray.class });
+ }
+ m.setAccessible(true);
+ int modifiers = m.getModifiers();
+ if ((m.getReturnType() != JsonObject.class &&
m.getReturnType() != JsonArray.class) || !Modifier.isStatic(modifiers) ||
!Modifier.isPublic(modifiers)) {
+ throw new NoSuchMethodException(mainMethodName);
+ }
+ mainMethod = m;
+
+ Object response;
+ if (isJsonObjectParam) {
+ response = invokeMain(payloadForJsonObject, env);
+ } else {
+ response = invokeMain(payloadForJsonArray, env);
+ }
out.println(response.toString());
} catch(NullPointerException npe) {
System.out.println("the action returned null");
diff --git
a/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
b/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
index 357885e..c09de29 100644
--- a/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
+++ b/tests/src/test/scala/actionContainers/JavaActionContainerTests.scala
@@ -157,9 +157,7 @@ class JavaActionContainerTests extends
BasicActionRunnerTests with WskActorSyste
val expected = m match {
case c if c == "x" || c == "!" => s"$errPrefix
java.lang.ClassNotFoundException: example.HelloWhisk$c"
- case "#bogus" =>
- s"$errPrefix java.lang.NoSuchMethodException:
example.HelloWhisk.bogus(com.google.gson.JsonObject)"
- case _ => s"$errPrefix java.lang.NoSuchMethodException:
example.HelloWhisk.main(com.google.gson.JsonObject)"
+ case _ => s"$errPrefix
java.lang.NoSuchMethodException"
}
val error = out.get.fields.get("error").get.toString()
@@ -301,6 +299,56 @@ class JavaActionContainerTests extends
BasicActionRunnerTests with WskActorSyste
})
}
+ it should "support return array result" in {
+ val (out, err) = withActionContainer() { c =>
+ val jar = JarBuilder.mkBase64Jar(
+ Seq("", "HelloArrayWhisk.java") ->
+ """
+ | import com.google.gson.JsonArray;
+ | import com.google.gson.JsonObject;
+ |
+ | public class HelloArrayWhisk {
+ | public static JsonArray main(JsonObject args) throws
Exception {
+ | JsonArray jsonArray = new JsonArray();
+ | jsonArray.add("a");
+ | jsonArray.add("b");
+ | return jsonArray;
+ | }
+ | }
+ """.stripMargin.trim)
+
+ val (initCode, _) = c.init(initPayload(jar, "HelloArrayWhisk"))
+ initCode should be(200)
+
+ val (runCode, runRes) = c.runForJsArray(runPayload(JsObject()))
+ runCode should be(200)
+ runRes shouldBe Some(JsArray(JsString("a"), JsString("b")))
+ }
+ }
+
+ it should "support array as input param" in {
+ val (out, err) = withActionContainer() { c =>
+ val jar = JarBuilder.mkBase64Jar(
+ Seq("", "HelloArrayWhisk.java") ->
+ """
+ | import com.google.gson.JsonArray;
+ |
+ | public class HelloArrayWhisk {
+ | public static JsonArray main(JsonArray args) throws
Exception {
+ | return args;
+ | }
+ | }
+ """.stripMargin.trim)
+
+ val (initCode, _) = c.init(initPayload(jar, "HelloArrayWhisk"))
+ initCode should be(200)
+
+ val (runCode, runRes) =
c.runForJsArray(runPayload(JsArray(JsString("a"), JsString("b"))))
+ runCode should be(200)
+ runRes shouldBe Some(JsArray(JsString("a"), JsString("b")))
+ }
+ }
+
it should "survive System.exit" in {
val (out, err) = withActionContainer() { c =>
val jar = JarBuilder.mkBase64Jar(