You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@openwhisk.apache.org by GitBox <gi...@apache.org> on 2018/01/30 18:13:00 UTC
[GitHub] csantanapr closed pull request #80: Test environment for local Openwhisk Java functions
csantanapr closed pull request #80: Test environment for local Openwhisk Java functions
URL: https://github.com/apache/incubator-openwhisk-devtools/pull/80
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/java-local/.gitignore b/java-local/.gitignore
new file mode 100644
index 0000000..6b7597d
--- /dev/null
+++ b/java-local/.gitignore
@@ -0,0 +1,6 @@
+.settings
+.project
+.classpath
+bin/
+.gradle/
+build/
diff --git a/java-local/README.md b/java-local/README.md
new file mode 100644
index 0000000..e232eea
--- /dev/null
+++ b/java-local/README.md
@@ -0,0 +1,36 @@
+# Testing Java functions locally
+
+## Building CLI
+```bash
+./gradlew jar
+```
+
+## Usage
+
+```bash
+java -jar ./build/libs/java-local.jar ./path-to-function.jar parameter=value parameter2=value2 --main=myproject.Myfunction
+```
+
+Will invoke the `main` function of `myproject.Myfunction` class from `path-to-function.jar` with following `params`:
+```json
+{
+ "parameter":"value",
+ "parameter2":"value"
+}
+```
+Alternatively, you can test a single Java file by directly invoking it. In this case the Java file should not require any third party libraries.
+
+```bash
+java -jar ./build/libs/java-local.jar ./myproject/Myfunction.java parameter=value parameter2=value2
+```
+
+This will always return a JSON formatted result that can be post-processed
+
+It is also possible to pass input on stdin, this allows the creation of more complex input
+objects that would be inconvenient to edit on the command line or passing non-string values.
+
+```bash
+echo '{"name": "value"}' | java -jar ./build/libs/java-local.jar ./path-to-function.jar --main=myproject.Myfunction
+cat input.json | java -jar ./build/libs/java-local.jar ../myproject/Myfunction.java
+```
+
diff --git a/java-local/build.gradle b/java-local/build.gradle
new file mode 100644
index 0000000..adb351c
--- /dev/null
+++ b/java-local/build.gradle
@@ -0,0 +1,25 @@
+apply plugin: 'java'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ compile 'com.google.code.gson:gson:2.6.2'
+ compile 'info.picocli:picocli:2.2.0'
+
+ // Use JUnit test framework
+ testImplementation 'junit:junit:4.12'
+}
+
+jar {
+ manifest {
+ attributes(
+ 'Main-Class': 'openwhisk.java.local.CLI',
+ )
+ }
+ from {
+ configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
+ }
+}
+
diff --git a/java-local/gradle/wrapper/gradle-wrapper.jar b/java-local/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..6b6ea3a
Binary files /dev/null and b/java-local/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/java-local/gradle/wrapper/gradle-wrapper.properties b/java-local/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..0e680f3
--- /dev/null
+++ b/java-local/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.3.1-bin.zip
diff --git a/java-local/gradlew b/java-local/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/java-local/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/java-local/gradlew.bat b/java-local/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/java-local/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/java-local/settings.gradle b/java-local/settings.gradle
new file mode 100644
index 0000000..4ba1597
--- /dev/null
+++ b/java-local/settings.gradle
@@ -0,0 +1,18 @@
+/*
+ * This settings file was generated by the Gradle 'init' task.
+ *
+ * The settings file is used to specify which projects to include in your build.
+ * In a single project build this file can be empty or even removed.
+ *
+ * Detailed information about configuring a multi-project build in Gradle can be found
+ * in the user guide at https://docs.gradle.org/4.3.1/userguide/multi_project_builds.html
+ */
+
+/*
+// To declare projects as part of a multi-project build use the 'include' method
+include 'shared'
+include 'api'
+include 'services:webservice'
+*/
+
+rootProject.name = 'java-local'
diff --git a/java-local/src/main/java/openwhisk/java/local/CLI.java b/java-local/src/main/java/openwhisk/java/local/CLI.java
new file mode 100644
index 0000000..60dd63f
--- /dev/null
+++ b/java-local/src/main/java/openwhisk/java/local/CLI.java
@@ -0,0 +1,121 @@
+/*
+ * 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 openwhisk.java.local;
+
+import java.io.File;
+import java.io.InputStreamReader;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.gson.stream.JsonReader;
+
+import picocli.CommandLine;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.ParameterException;
+import picocli.CommandLine.Parameters;
+
+/**
+ * CLI
+ */
+public class CLI {
+
+ @Parameters(index = "0", description = "The file to execute can be a .jar or .java file")
+ private String binary;
+
+ @Parameters(index = "1..*", description = "Parameters that are passed to function", paramLabel = "param=value")
+ private Map<String, String> parameters;
+
+ @Option(names = { "--main" }, required = false, description = "name of the main class, required if using .jar")
+ private String mainClassName;
+
+ public static void main(String[] args) {
+
+ CLI cli = new CLI();
+ try {
+ CommandLine.populateCommand(cli, args);
+
+ Path path = Paths.get(cli.binary).toRealPath();
+ final boolean isJar = path.toString().endsWith(".jar");
+ final boolean isJava = path.toString().endsWith(".java");
+ if (!isJava && !isJar) {
+ System.out.printf("%s is not a jar or java file\n", cli.binary);
+ CommandLine.usage(cli, System.out);
+ return;
+ }
+
+ File f = path.toFile();
+ if (!f.canRead()) {
+ System.out.printf("%s does not exist or can not be read\n", cli.binary);
+ return;
+ }
+
+ Launcher launcher = new Launcher();
+ launcher.setBinaryPath(path);
+ launcher.setEntryClassName(cli.mainClassName);
+ launcher.setParameter(readParameters(cli));
+ JsonObject o = launcher.launch();
+ Gson gson = new Gson();
+ System.out.println(gson.toJson(o));
+ System.exit(0);
+
+ } catch (ParameterException e) {
+ System.out.println(e.getMessage());
+ CommandLine.usage(cli, System.out);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ private static JsonObject readParameters(CLI cli) {
+ if (cli.parameters == null || cli.parameters.isEmpty()) {
+ ExecutorService ex = Executors.newSingleThreadExecutor();
+ Future<JsonObject> result = ex.submit(() -> {
+ try(JsonReader reader = new JsonReader(new InputStreamReader(System.in))){
+ reader.setLenient(true);
+ JsonParser parser = new JsonParser();
+ JsonElement element = parser.parse(reader);
+ return element.getAsJsonObject();
+ }
+ });
+ try {
+ return result.get(1, TimeUnit.SECONDS);
+ } catch (InterruptedException | ExecutionException | TimeoutException e) {
+ result.cancel(true);
+ return new JsonObject();
+ }
+
+ } else {
+ JsonObject o = new JsonObject();
+ cli.parameters.forEach((name, value) -> {
+ o.addProperty(name, value);
+ });
+ return o;
+ }
+ }
+}
diff --git a/java-local/src/main/java/openwhisk/java/local/JarLoader.java b/java-local/src/main/java/openwhisk/java/local/JarLoader.java
new file mode 100644
index 0000000..a89c181
--- /dev/null
+++ b/java-local/src/main/java/openwhisk/java/local/JarLoader.java
@@ -0,0 +1,63 @@
+/*
+ * 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 openwhisk.java.local;
+
+import com.google.gson.JsonObject;
+
+import openwhisk.java.local.Launcher.Invoker;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+
+/**
+ * Parts copied from https://github.com/apache/incubator-openwhisk-runtime-java/blob/master/core/javaAction/proxy/src/main/java/openwhisk/java/action/JarLoader.java
+ *
+ */
+public class JarLoader extends URLClassLoader implements Invoker{
+
+ private Class<?> mainClass;
+ private Method mainMethod;
+
+ public JarLoader(Path jarPath, String entrypoint)
+ throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, SecurityException {
+ super(new URL[] { jarPath.toUri().toURL() });
+
+ 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.mainMethod = m;
+ }
+
+ @Override
+ public JsonObject invokeMain(JsonObject arg) throws Exception {
+ return (JsonObject) mainMethod.invoke(null, arg);
+ }
+
+}
diff --git a/java-local/src/main/java/openwhisk/java/local/JavaCompilerLoader.java b/java-local/src/main/java/openwhisk/java/local/JavaCompilerLoader.java
new file mode 100644
index 0000000..b084d67
--- /dev/null
+++ b/java-local/src/main/java/openwhisk/java/local/JavaCompilerLoader.java
@@ -0,0 +1,136 @@
+/*
+ * 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 openwhisk.java.local;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
+import java.util.List;
+
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaCompiler.CompilationTask;
+import javax.tools.JavaFileObject;
+import javax.tools.JavaFileObject.Kind;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+
+import com.google.gson.JsonObject;
+
+import openwhisk.java.local.Launcher.Invoker;
+
+/**
+ * JavaCompilerLoader
+ */
+public class JavaCompilerLoader extends ClassLoader implements Invoker {
+ private MemoryFileManager fileManager;
+ private Class<?> mainClass;
+ private Method mainMethod;
+
+ public JavaCompilerLoader(ClassLoader loader, Path sourcePath) {
+ super(loader);
+ JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ if (compiler == null) {
+ throw new RuntimeException("No java compiler. Please use a JDK not a JRE!");
+ }
+
+ try {
+ Path resolvedPath = sourcePath.toAbsolutePath();
+ DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
+ String packageName = findPackageName(resolvedPath);
+ String fileName = sourcePath.getFileName().toString();
+ String className = fileName.substring(0,fileName.length()-Kind.SOURCE.extension.length());
+ if(!packageName.isEmpty())
+ {
+ className = packageName + '.' + className;
+ }
+ File sourceRoot = findSourceRoot(packageName, resolvedPath);
+ standardFileManager.setLocation(StandardLocation.SOURCE_PATH, Collections.singleton(sourceRoot));
+
+ JavaFileObject javaFile = standardFileManager.getJavaFileForInput(StandardLocation.SOURCE_PATH, className, Kind.SOURCE);
+ if(javaFile == null ){
+ System.err.printf("java file for classname does not exist file: %s\n", className);
+ return;
+ }
+ fileManager = new MemoryFileManager(loader, standardFileManager);
+ CompilationTask task = compiler.getTask(null, fileManager, diagnostics, Collections.singleton("-g"), null, Collections.singleton(javaFile));
+ boolean valid = task.call();
+ if (valid) {
+ diagnostics.getDiagnostics().forEach(System.out::println);
+ } else {
+ diagnostics.getDiagnostics().forEach(System.err::println);
+ throw new RuntimeException(String.format("Compilation for the file %s failed!\n",sourcePath.toString()));
+ }
+
+ this.mainClass = loadClass(className);
+
+ Method m = mainClass.getMethod("main", 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.mainMethod = m;
+
+ } catch (IOException | ClassNotFoundException | NoSuchMethodException | SecurityException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public JsonObject invokeMain(JsonObject arg) throws Exception {
+ return (JsonObject) mainMethod.invoke(null, arg);
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ byte[] bytecode = getClassBytes(name);
+ if (bytecode == null) {
+ throw new ClassNotFoundException(name);
+ }
+ return defineClass(name, bytecode, 0, bytecode.length);
+ }
+
+ public byte[] getClassBytes(String name) {
+ return fileManager.getCompiledClass(name);
+ }
+
+ private String findPackageName(Path sourcePath) throws IOException {
+ List<String> lines = Files.readAllLines(sourcePath.toAbsolutePath(), StandardCharsets.UTF_8);
+ for (String line : lines) {
+ if(line.startsWith("package ")){
+ int idx = line.indexOf("package ");
+ return line.substring(line.indexOf(' ', idx), line.indexOf(';', idx)).trim();
+ }
+ }
+ return "";
+ }
+
+ private File findSourceRoot(String packageName, Path sourcePath) throws IOException {
+ int packageSections = Math.toIntExact(packageName.codePoints().filter(ch -> ch =='.').count()) + 1 ;
+ packageSections++;//increment to include actual filename
+ return sourcePath.getRoot().resolve(sourcePath.subpath(0, sourcePath.getNameCount()-packageSections)).toFile();
+ }
+
+}
diff --git a/java-local/src/main/java/openwhisk/java/local/Launcher.java b/java-local/src/main/java/openwhisk/java/local/Launcher.java
new file mode 100644
index 0000000..6d78b10
--- /dev/null
+++ b/java-local/src/main/java/openwhisk/java/local/Launcher.java
@@ -0,0 +1,108 @@
+/*
+ * 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 openwhisk.java.local;
+
+import java.io.IOException;
+import java.nio.file.FileSystems;
+import java.nio.file.Path;
+import java.nio.file.PathMatcher;
+
+import com.google.gson.JsonObject;
+
+/**
+ * Local launcher for openwhisk functions
+ *
+ */
+public class Launcher {
+
+ private Path binaryPath;
+ private JsonObject parameter;
+ private String entryClassName;
+ private static PathMatcher JAVA_FILE_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**.java");
+
+ public interface Invoker {
+ public JsonObject invokeMain(JsonObject arg) throws Exception;
+ }
+
+ /**
+ * @return the binaryPath
+ */
+ public Path getBinaryPath() {
+ return binaryPath;
+ }
+
+ /**
+ * @param binaryPath the binaryPath to set
+ */
+ public void setBinaryPath(Path binaryPath) {
+ try {
+ this.binaryPath = binaryPath.toRealPath();
+ } catch (IOException e) {
+ this.binaryPath = binaryPath;
+ }
+ }
+
+ /**
+ * @return the parameters
+ */
+ public JsonObject getParameter() {
+ return parameter;
+ }
+
+ /**
+ * @param parameters the parameters to set
+ */
+ public void setParameter(JsonObject parameter) {
+ this.parameter = parameter;
+ }
+
+ /**
+ * @return the entryClassName
+ */
+ public String getEntryClassName() {
+ return entryClassName;
+ }
+
+ /**
+ * @param entryClassName the entryClassName to set
+ */
+ public void setEntryClassName(String entryClassName) {
+ this.entryClassName = entryClassName;
+ }
+
+ public JsonObject launch() throws Exception {
+ Invoker invoker;
+ ClassLoader loader;
+ if (JAVA_FILE_MATCHER.matches(this.binaryPath)) {
+ loader = new JavaCompilerLoader(Thread.currentThread().getContextClassLoader(), this.getBinaryPath());
+ invoker = (Invoker) loader;
+ } else {
+ if (getEntryClassName() == null) {
+ throw new IllegalStateException("Main class name is required to execute .jar");
+ }
+ loader = new JarLoader(this.getBinaryPath(), getEntryClassName());
+ invoker = (Invoker) loader;
+ }
+ Thread.currentThread().setContextClassLoader(loader);
+ JsonObject args = getParameter();
+ if (args == null) {
+ args = new JsonObject(); //always pass an argument
+ }
+ return invoker.invokeMain(args);
+ }
+
+}
diff --git a/java-local/src/main/java/openwhisk/java/local/MemoryFileManager.java b/java-local/src/main/java/openwhisk/java/local/MemoryFileManager.java
new file mode 100644
index 0000000..4bcf83c
--- /dev/null
+++ b/java-local/src/main/java/openwhisk/java/local/MemoryFileManager.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 openwhisk.java.local;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.tools.FileObject;
+import javax.tools.ForwardingJavaFileManager;
+import javax.tools.JavaFileManager;
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+
+/**
+ * Keeps compiled class in memory
+ *
+ */
+public class MemoryFileManager extends ForwardingJavaFileManager<JavaFileManager> {
+ private final Map<String, ByteArrayOutputStream> compiledClasses = new HashMap<>();
+
+ public MemoryFileManager(ClassLoader classLoader, JavaFileManager fileManager) {
+ super(fileManager);
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(Location location, final String className,
+ JavaFileObject.Kind kind, FileObject sibling) throws IOException {
+ try {
+ return new SimpleJavaFileObject(new URI(""), kind) {
+ public OutputStream openOutputStream() throws IOException {
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ compiledClasses.put(className, outputStream);
+ return outputStream;
+ }
+ };
+ } catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public byte[] getCompiledClass(String name) {
+ ByteArrayOutputStream bytes = compiledClasses.get(name);
+ if (bytes == null) {
+ return null;
+ }
+ return bytes.toByteArray();
+ }
+}
diff --git a/java-local/src/test/java/openwhisk/java/local/test/LauncherTest.java b/java-local/src/test/java/openwhisk/java/local/test/LauncherTest.java
new file mode 100644
index 0000000..f4f60fb
--- /dev/null
+++ b/java-local/src/test/java/openwhisk/java/local/test/LauncherTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 openwhisk.java.local.test;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+
+import com.google.gson.JsonObject;
+
+import openwhisk.java.local.Launcher;
+
+/**
+ * LauncherTest
+ */
+public class LauncherTest {
+
+ @Test
+ public void testLauncherParameters(){
+ Launcher launcher = new Launcher();
+ final Path binaryPath = Paths.get("/");
+
+ assertNull(launcher.getBinaryPath());
+ launcher.setBinaryPath(binaryPath);
+ assertTrue(launcher.getBinaryPath().isAbsolute());
+
+ final String className = "clazzname";
+ assertNull(launcher.getEntryClassName());
+ launcher.setEntryClassName(className);
+ assertSame(className, launcher.getEntryClassName());
+
+ final JsonObject param = new JsonObject();
+ assertNull(launcher.getParameter());
+ launcher.setParameter(param);
+ assertSame(param, launcher.getParameter());
+
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testLaunchJarNoMain() throws Exception{
+ Launcher launcher = new Launcher();
+ Path jarPath = Paths.get("someJar.jar");
+ System.out.println(jarPath.toAbsolutePath().toString());
+ launcher.setBinaryPath(jarPath);
+ launcher.launch();
+ fail();
+ }
+
+
+ @Test
+ public void testRelativePathForJavaFile() throws Exception{
+ Launcher launcher = new Launcher();
+ Path filePath = Paths.get("./src/test/resources/aproject/App.java");
+ launcher.setBinaryPath(filePath);
+ JsonObject result = launcher.launch();
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testAbsolutePathForJavaFile() throws Exception{
+ Launcher launcher = new Launcher();
+ Path filePath = Paths.get("./src/test/resources/aproject/App.java");
+ launcher.setBinaryPath(filePath.toRealPath());
+ JsonObject result = launcher.launch();
+ assertNotNull(result);
+ }
+
+
+
+ @Test
+ public void testAbsolutePathForJarLaunch() throws Exception{
+ Launcher launcher = new Launcher();
+ Path jarPath = Paths.get("./src/test/resources/serverlessJava.jar");
+ launcher.setBinaryPath(jarPath.toRealPath());
+ launcher.setEntryClassName("aproject.App");
+ JsonObject result = launcher.launch();
+ assertNotNull(result);
+ }
+
+ @Test
+ public void testRelativePathForJarLaunch() throws Exception{
+ Launcher launcher = new Launcher();
+ Path jarPath = Paths.get("./src/test/resources/serverlessJava.jar");
+ launcher.setBinaryPath(jarPath);
+ launcher.setEntryClassName("aproject.App");
+ JsonObject result = launcher.launch();
+ assertNotNull(result);
+ }
+}
+
diff --git a/java-local/src/test/resources/aproject/App.java b/java-local/src/test/resources/aproject/App.java
new file mode 100644
index 0000000..6eefc63
--- /dev/null
+++ b/java-local/src/test/resources/aproject/App.java
@@ -0,0 +1,18 @@
+package aproject;
+
+import com.google.gson.JsonObject;
+
+/*
+ * This Java source file was generated by the Gradle 'init' task.
+ */
+public class App {
+
+ public static JsonObject main(JsonObject args) {
+ String name = "stranger";
+ if (args.has("name"))
+ name = args.getAsJsonPrimitive("name").getAsString();
+ JsonObject response = new JsonObject();
+ response.addProperty("greeting", "Hello " + name + "!");
+ return response;
+ }
+}
diff --git a/java-local/src/test/resources/serverlessJava.jar b/java-local/src/test/resources/serverlessJava.jar
new file mode 100644
index 0000000..0061fac
Binary files /dev/null and b/java-local/src/test/resources/serverlessJava.jar differ
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
users@infra.apache.org
With regards,
Apache Git Services