Skip to content
This repository was archived by the owner on Jan 8, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions scripts/build_exp_abstract_class_impl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/bin/env bash

# A build script for the experiment for actions implementing abstract classes.

SECONDS_SINCE_EPOCH=$(printf '%(%s)T\n' -1)

TAG="owextendedruntimes/java-17:experiment-abstract-class-impl-${SECONDS_SINCE_EPOCH}"

docker build -t $TAG .
docker push $TAG
47 changes: 16 additions & 31 deletions src/main/java/com/mattwelke/aoer/java17/Proxy.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
Expand All @@ -25,6 +24,7 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.owextendedruntimes.actiontest.Action;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
Expand All @@ -41,6 +41,8 @@ public class Proxy {

private JarLoader loader = null;

private Action userAction = null;

public Proxy(int port) throws IOException {
this.server = HttpServer.create(new InetSocketAddress(port), -1);

Expand Down Expand Up @@ -150,21 +152,15 @@ public void handle(HttpExchange t) throws IOException {
System.setSecurityManager(new WhiskSecurityManager());

// User code starts running here.
Map<String, Object> output = loader.invokeMain(value, owVars);
userAction.setClusterContext(owVars);
Map<String, Object> output = userAction.invoke(value);
// User code finished running here.

if (output == null) {
throw new NullPointerException("The action returned null");
}

Proxy.writeResponse(t, 200, new Gson().toJson(output));
} catch (InvocationTargetException ite) {
// These are exceptions from the action, wrapped in ite because
// of reflection
Throwable underlying = ite.getCause();
underlying.printStackTrace(System.err);
Proxy.writeError(t,
"An error has occurred while invoking the action (see logs for details): " + underlying);
} catch (Exception e) {
e.printStackTrace(System.err);
Proxy.writeError(t, "An error has occurred (see logs for details): " + e);
Expand All @@ -180,9 +176,7 @@ public void handle(HttpExchange t) throws IOException {
* Custom JAR loader.
* Based on https://github.com/apache/openwhisk-runtime-java/blob/master/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
*/
private static class JarLoader extends URLClassLoader {
private final Method mainMethod;

private class JarLoader extends URLClassLoader {
public static Path saveBase64EncodedFile(InputStream encoded) throws Exception {
Base64.Decoder decoder = Base64.getDecoder();

Expand All @@ -197,27 +191,18 @@ public static Path saveBase64EncodedFile(InputStream encoded) throws Exception {
return destinationPath;
}

public JarLoader(Path jarPath, String entrypoint)
throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, SecurityException {
super(new URL[] { jarPath.toUri().toURL() });

final String[] splitEntrypoint = entrypoint.split("#");
final String entrypointClassName = splitEntrypoint[0];
final String entrypointMethodName = splitEntrypoint.length > 1 ? splitEntrypoint[1] : "main";
public JarLoader(Path jarPath, String actionClassName)
throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, SecurityException,
InvocationTargetException, InstantiationException, IllegalAccessException {

Class<?> mainClass = loadClass(entrypointClassName);
super(new URL[] { jarPath.toUri().toURL() });

Method m = mainClass.getMethod(entrypointMethodName, new Class[] { Map.class, Map.class });
m.setAccessible(true);
int modifiers = m.getModifiers();
if (m.getReturnType() != Map.class || !Modifier.isStatic(modifiers) || !Modifier.isPublic(modifiers)) {
throw new NoSuchMethodException("main");
}
this.mainMethod = m;
}
// Use reflection to create an instance of the user's action.
Class<? extends Action> actionClass = loadClass(actionClassName).asSubclass(Action.class);
Constructor<? extends Action> actionClassConstructor = actionClass.getConstructor();

public Map<String, Object> invokeMain(Map<String, Object> value, Map<String, Object> owVars) throws Exception {
return (Map<String, Object>) mainMethod.invoke(null, value, owVars);
// Associate action instance with Proxy instance so that it can be used in the run handler.
Proxy.this.userAction = actionClassConstructor.newInstance();
}
}

Expand Down
32 changes: 32 additions & 0 deletions src/main/java/com/owextendedruntimes/actiontest/Action.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.owextendedruntimes.actiontest;

import java.util.Map;

/**
* Parent class of all OpenWhisk Java action implementations. OpenWhisk users extend this class and override the invoke
* method to implement their actions.
*/
public abstract class Action {
/**
* The cluster context, aka "OpenWhisk variables", implemented in official runtimes by mutating the environment
* variables at runtime (no longer allowed by Java 9+). In this runtime, implemented as state contained within the
* parent class of the user's action.
*/
protected Map<String, Object> clusterContext;

/**
* Sets the cluster context of the action so that it can be referenced by all invocations of the action if the
* OpenWhisk user wants to use it. This function should not be called by the OpenWhisk user.
* @param clusterContext The cluster context.
*/
public void setClusterContext(Map<String, Object> clusterContext) {
this.clusterContext = clusterContext;
}

/**
* The method invoked by the OpenWhisk runtime in response to the action being invoked.
* @param input The input to the action during its invocation.
* @return The output of the action invocation.
*/
public abstract Map<String, Object> invoke(Map<String, Object> input);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.owextendedruntimes.actiontestimpl;

import com.owextendedruntimes.actiontest.Action;

import java.util.Map;

// A test of implementing the new action contract as a user would in their own Java project.
public class ActionImpl extends Action {
@Override
public Map<String, Object> invoke(Map<String, Object> input) {
if (clusterContext.containsKey("clusterName")) {
return Map.of("clusterName", clusterContext.get("clusterName"));
}
return Map.of("hello", "world");
}
}