Allow multiple inits of java runtime to enable cache priming (#289)

diff --git a/knative-build/runtimes/java/WhiskSim/.classpath b/knative-build/runtimes/java/WhiskSim/.classpath
index f0257c5..71f5fef 100644
--- a/knative-build/runtimes/java/WhiskSim/.classpath
+++ b/knative-build/runtimes/java/WhiskSim/.classpath
@@ -13,7 +13,7 @@
 			<attribute name="test" value="true"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7">
 		<attributes>
 			<attribute name="maven.pomderived" value="true"/>
 		</attributes>
diff --git a/knative-build/runtimes/java/core/java8/proxy/compileClassCache.sh b/knative-build/runtimes/java/core/java8/proxy/compileClassCache.sh
index 503adfc..5ecc9b0 100755
--- a/knative-build/runtimes/java/core/java8/proxy/compileClassCache.sh
+++ b/knative-build/runtimes/java/core/java8/proxy/compileClassCache.sh
@@ -40,6 +40,8 @@
 JAVA_VERBOSE_OPTIONS="-verbose:class -verbose:sizes"
 JAVA_JVM_KILL_DELAY=5s
 
+export OW_ALLOW_MULTIPLE_INIT=true
+
 echo "Creating shared class cache with Proxy and 'base' profile libraries..."
 java $JAVA_VERBOSE_OPTIONS $JAVA_STANDARD_OPTIONS $JAVA_EXTENDED_OPTIONS "-jar" "/javaAction/build/libs/javaAction-all.jar" &
 HTTP_PID=$!
diff --git a/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/exec_tests.sh b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/exec_tests.sh
new file mode 100755
index 0000000..5db373a
--- /dev/null
+++ b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/exec_tests.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# 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.
+#
+set +x
+
+for f in *; do
+    # if the file is a directory
+    if [ -d ${f} ]; then
+        echo "Updating 'code' payload with base64 encoded archive data: ${f}"
+        cd $f
+            sed "s#BASE64_ENCODED_JAR#$(cat hello.jar.base64)#" openwhisk-data-init.json.tmpl > openwhisk-data-init.json
+            sed "s#BASE64_ENCODED_JAR#$(cat hello.jar.base64)#" knative-data-init.json.tmpl > knative-data-init.json
+            sed "s#BASE64_ENCODED_JAR#$(cat hello.jar.base64)#" knative-data-init-run.json.tmpl > native-data-init-run.json
+            sed "s#BASE64_ENCODED_JAR#$(cat hello.jar.base64)#" payload-knative-init.http.tmpl > payload-knative-init.http
+            sed "s#BASE64_ENCODED_JAR#$(cat hello.jar.base64)#" payload-knative-init-run.http.tmpl > payload-knative-init-run.http
+            sed "s#BASE64_ENCODED_JAR#$(cat hello.jar.base64)#" payload-openwhisk-init.http.tmpl > payload-openwhisk-init.http
+        cd ..
+    fi
+done
diff --git a/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworld/native-data-init-run.json b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworld/native-data-init-run.json
new file mode 100644
index 0000000..30282d3
--- /dev/null
+++ b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworld/native-data-init-run.json
@@ -0,0 +1,18 @@
+{
+  "init": {
+    "name": "java-helloworld",
+    "main": "Hello",
+    "binary": true,
+    "code" : "UEsDBBQACAgIAA18LU8AAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAAAAAAFBLAwQUAAgICAANfC1PAAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0xLLS7RDUstKs7Mz7NSMNQz4OVyLkpNLElN0XWqBAlY6BnEGxkYKWi4JudkFhSnKvgXpOZ5WWrycvFyAQBQSwcIMFMkp0EAAABBAAAAUEsDBBQACAgIAA18LU8AAAAAAAAAAAAAAAALAAAASGVsbG8uY2xhc3N9UmtP02AUfg67tHSdzAJD0CHzRjcGVREvjJCIiTFmKMkMiX57t72pxV6WriP6U/wX+mUkkvgD/E3GeN7GaYKDJj2X9znnec572h+/vn0H8ABNAxYqOpZ1XDegYUWZqoEbuKnhlkoYvK3hjgFDhavK2zpqytcVvqahoWGdkN/xQi/ZJWTs2iEh+yzqScJMywvlq2HQkfEb0fH5JBsILyRs261uFDhuFLm+dNxBFDov2bzuHMlu0qxdBBKK7UR0P+yLfsqpYYNgtKNh3JXPPaVhvJC+H20ciWNhYhZzBH2QxCJ0ZcwDhCKQJhzcNXEP9zVsmryJLcLS+ZpM4MZSJl7oEhYUr+MzndNOYj7aG3p+T1HnU90VEw/xiEBVE4+xZeIJtgm5FCOU/nWPuUtnCXmJ78WAMG+3zkLN2juC5crk6UCNdxB7gZd4x3zpzUnFE/f4t4lXWbmwgFBIpcZjzdm1/0X43qLfl2GPsD5xhnP2xY16Eo2pC6LXO4ijvoyTT4TVCUQTqA9RxWX+hdUzBVLfmu08ZxX2xD5XPwF95YBQZptPD2cxjQVc+VP6EVlk2O9aU626lRkh+xnFNMiNkG+dQnt7An2/YU2fwuCwYJkjFNf4tS6xGWFmhFLjSzqC0lhiFTBzhjOTdcqstIxF7DCyyIgO+omyxhnhatp17TdQSwcIxFn0If4BAACRAwAAUEsBAhQAFAAICAgADXwtTwAAAAACAAAAAAAAAAkABAAAAAAAAAAAAAAAAAAAAE1FVEEtSU5GL/7KAABQSwECFAAUAAgICAANfC1PMFMkp0EAAABBAAAAFAAAAAAAAAAAAAAAAAA9AAAATUVUQS1JTkYvTUFOSUZFU1QuTUZQSwECFAAUAAgICAANfC1PxFn0If4BAACRAwAACwAAAAAAAAAAAAAAAADAAAAASGVsbG8uY2xhc3NQSwUGAAAAAAMAAwC2AAAA9wIAAAAA"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "java-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+  }
+}
diff --git a/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworldwithparams/native-data-init-run.json b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworldwithparams/native-data-init-run.json
new file mode 100644
index 0000000..752b692
--- /dev/null
+++ b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworldwithparams/native-data-init-run.json
@@ -0,0 +1,20 @@
+{
+    "init": {
+        "name" : "java-helloworld-with-params",
+        "main" : "Hello",
+        "binary": true,
+        "code" : "UEsDBBQACAgIAA18LU8AAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAAAAAAFBLAwQUAAgICAANfC1PAAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0xLLS7RDUstKs7Mz7NSMNQz4OVyLkpNLElN0XWqBAlY6BnEGxkYKWi4JudkFhSnKvgXpOZ5WWrycvFyAQBQSwcIMFMkp0EAAABBAAAAUEsDBBQACAgIAA18LU8AAAAAAAAAAAAAAAALAAAASGVsbG8uY2xhc3N9Uu1uEkEUPcPXLssiuG2t1KJQtQUKRW39KrW1mhhjoDbBNFHjjwHG7db9IMtS46P4BP7VPzSxiQ/gO2m8s4omlXZn986dO2fPOXMz339+/QZgDdsaZlBQUVSxoOKqhiSuyXBdwyKWVJQUlOWaIBUFyxp0mVblXFOxoqIu0xsSclPBLQWrDIkNy7WCTYZoqbzHEHvs9QRDpmm5YmfodIT/gndsqsQcbrkM66Vm13PqpueZtqibA8+tP6PwvHMgukGjfNYmQ7od8O67Fu+HnArWGLS2N/S74oklNbSnwra9lQN+yHVcwCyDOgh87prCZ0gOPEe83xe+NONyR+i4jTs67uKegvs61kEK8b7NuwSYO90HkZq+EIHlmgyzUqtuk0S9HfhUejS07J6US4ReCjo28IBWhbe+5xQYWFHHJho6tvCQ5EIQQ/YfzVgke5KZOrzPBwwzpebJrUb5FYNhimB7IH3u+pZjBdYhnWN1Enhik//+RF3InwlgSIVSY1vTpfL/InRk3u8Lt8dQm+jhlMbRj2rgjalTvNfb9b2+8IMPDEsTiCZQ76GIabro8omAyYtA8SKt8jQzmuOVI7AvlDDkKCbC4hTd6jlc+gP9hBgN4I0RaRrRVsWIjRD/iHSYJEZQmhVDHZfU36XWMZIvj6Dt1IzUMXRK08a5ETLL9BlZClWZnKcwgjHCVO1z6FBaWCRxkOcouU2Q3wyZyNFYIEtVMrWGebwm//OE0hH5gZyCPL0G2b0cslz5BVBLBwistPL2OQIAAOYDAABQSwECFAAUAAgICAANfC1PAAAAAAIAAAAAAAAACQAEAAAAAAAAAAAAAAAAAAAATUVUQS1JTkYv/soAAFBLAQIUABQACAgIAA18LU8wUySnQQAAAEEAAAAUAAAAAAAAAAAAAAAAAD0AAABNRVRBLUlORi9NQU5JRkVTVC5NRlBLAQIUABQACAgIAA18LU+stPL2OQIAAOYDAAALAAAAAAAAAAAAAAAAAMAAAABIZWxsby5jbGFzc1BLBQYAAAAAAwADALYAAAAyAwAAAAA="
+    },
+    "activation": {
+        "namespace": "default",
+        "action_name": "java-helloworld-with-params",
+        "api_host": "",
+        "api_key": "",
+        "activation_id": "",
+        "deadline": "4102498800000"
+    },
+    "value": {
+        "name" : "Joe",
+        "place" : "TX"
+    }
+}
diff --git a/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworldwithparamsfromenv/native-data-init-run.json b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworldwithparamsfromenv/native-data-init-run.json
new file mode 100644
index 0000000..ca94e97
--- /dev/null
+++ b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/helloworldwithparamsfromenv/native-data-init-run.json
@@ -0,0 +1,20 @@
+{
+    "init": {
+        "name" : "java-helloworld-with-params-from-env",
+        "main" : "Hello",
+        "binary": true,
+        "code" : "UEsDBBQACAgIAA58LU8AAAAAAAAAAAAAAAAJAAQATUVUQS1JTkYv/soAAAMAUEsHCAAAAAACAAAAAAAAAFBLAwQUAAgICAAOfC1PAAAAAAAAAAAAAAAAFAAAAE1FVEEtSU5GL01BTklGRVNULk1G803My0xLLS7RDUstKs7Mz7NSMNQz4OVyLkpNLElN0XWqBAlY6BnEGxkYKWi4JudkFhSnKvgXpOZ5WWrycvFyAQBQSwcIMFMkp0EAAABBAAAAUEsDBBQACAgIAA58LU8AAAAAAAAAAAAAAAALAAAASGVsbG8uY2xhc3ONUttu00AUnNMmsesaWtzQGxQSLq3TNjW3Jwo8gIQQCgUpqBKPm/hgubJ3I9ep1L+ClxRRiQ/goxBnQ7kIAsKWZ2dnz5lZ2/v5y8dPAO7hrocLWHVxycNlrLm44uCqh6qVGg6aHlxLr9nxuosbLm5aum5LNhyEDlqE2oNUp+UjwnTY2idUnpiYCXOdVPPeMO9x8Vr1MlEquUo14X7Y6Zs8SoxJMo6SQ6Oj5wIvewfcL3db/1okeF0zLPr8NLWG3jPOMrNzoI6UjwALEqFVzg42fWxhm1AdZKovhat/9yS4ScFcpjohLFmrKFM6ibplIdLjYZrFXMg7jqMaPtrYkVnjbWHyBoGaPiLc8nEbdyRuXESY/2nzPeQXqXt8WHIuJgmXrI8I62Hn91z5DH9I0qEGA9Yxof1fHWebl0a3NN8kQj2caD2r4vhVYQZclMeEjQn+ExL30cS8nB97TYHsLxCsy2xNRpKxunkCei+EcFGwNhYXMINFLJ2VMipyAw+DqQ+Y7gQVwRenqL45QW2vHTincIXOBN4Is1vyBL7AtiXnBEY4P8Jc+92PiEU5mRBWE+ZLSB3L2MWKrC6Pd7nyFVBLBwhnUa6bvgEAAPsCAABQSwECFAAUAAgICAAOfC1PAAAAAAIAAAAAAAAACQAEAAAAAAAAAAAAAAAAAAAATUVUQS1JTkYv/soAAFBLAQIUABQACAgIAA58LU8wUySnQQAAAEEAAAAUAAAAAAAAAAAAAAAAAD0AAABNRVRBLUlORi9NQU5JRkVTVC5NRlBLAQIUABQACAgIAA58LU9nUa6bvgEAAPsCAAALAAAAAAAAAAAAAAAAAMAAAABIZWxsby5jbGFzc1BLBQYAAAAAAwADALYAAAC3AgAAAAA="
+    },
+    "activation": {
+        "namespace": "default",
+        "action_name": "java-helloworld-with-params-from-env",
+        "api_host": "",
+        "api_key": "",
+        "activation_id": "",
+        "deadline": "4102498800000"
+    },
+    "value": {
+        "name" : "Jess",
+        "place" : "OK"
+    }
+}
diff --git a/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/test-list-all.txt b/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/test-list-all.txt
deleted file mode 100644
index ea8521b..0000000
--- a/knative-build/runtimes/java/core/java8/proxy/profiles/base/tests/test-list-all.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-helloworld
-helloworldwithparams
-helloworldwithparamsfromenv
diff --git a/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Debug.java b/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Debug.java
index e32afc9..287107a 100644
--- a/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Debug.java
+++ b/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Debug.java
@@ -16,7 +16,7 @@
  */
 package org.apache.openwhisk.runtime.java.action;
 
-import java.time.*;
+import java.util.Map;
 
 public class Debug {
 
@@ -50,8 +50,6 @@
     private static String FQ_METHOD = "";
     private static String METHOD = "";
     private static long currentTime = 0;
-    //private static long startTime = 0;
-    //private static long stopTime = 0;
 
     private static long updateContext(){
         currentTime = System.nanoTime();
@@ -99,11 +97,20 @@
     public static long end() { return end("",-1);}
     public static long end(long startTime) { return end("", startTime);}
 
-    public static long end(String msg, long startTime){
+    public static long end(String msg, long startTime) {
         currentTime = updateContext();
         String formattedMsg = formatMessage(functionEndMarker, msg, startTime);
         System.out.println(formattedMsg);
         return currentTime;
     }
 
+    public static void printEnv() {
+        Map<String, String> envVars = System.getenv();
+        long ts = System.nanoTime();
+        System.out.printf("%sEnvironment Variables (%d):%s\n", prefixFGColor, ts, bodyFGColor);
+        for (String key : envVars.keySet()) {
+            System.out.printf(">> %s=\"%s\"%n", key, envVars.get(key));
+        }
+    }
+
 }
diff --git a/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java b/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
index 03f7c90..21f4afb 100644
--- a/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
+++ b/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/JarLoader.java
@@ -35,8 +35,8 @@
 import com.google.gson.JsonObject;
 
 public class JarLoader extends URLClassLoader {
-    private final Class<?> mainClass;
-    private final Method mainMethod;
+    private Class<?> mainClass = null;
+    private Method mainMethod = null;
 
     public static Path saveBase64EncodedFile(InputStream encoded) throws Exception {
         Base64.Decoder decoder = Base64.getDecoder();
@@ -55,7 +55,10 @@
     public JarLoader(Path jarPath, String entrypoint)
             throws MalformedURLException, ClassNotFoundException, NoSuchMethodException, SecurityException {
         super(new URL[] { jarPath.toUri().toURL() });
+        loadMainClassAndMethod(entrypoint);
+    }
 
+    public void loadMainClassAndMethod(String entrypoint) throws NoSuchMethodException, ClassNotFoundException {
         final String[] splittedEntrypoint = entrypoint.split("#");
         final String entrypointClassName = splittedEntrypoint[0];
         final String entrypointMethodName = splittedEntrypoint.length > 1 ? splittedEntrypoint[1] : "main";
@@ -69,7 +72,20 @@
             throw new NoSuchMethodException("main");
         }
 
-        this.mainMethod = m;
+        mainMethod = m;
+    }
+
+    public void addJAR(Path jarPath) throws MalformedURLException {
+        if(jarPath!=null){
+            try{
+                this.addURL(jarPath.toUri().toURL());
+            } catch (MalformedURLException e){
+                System.err.format("Invalid JAR file path. [%s]", jarPath);
+                throw e;
+            }
+        } else {
+            System.err.format("Invalid JAR file path. [%s]", jarPath);
+        }
     }
 
     public JsonObject invokeMain(JsonObject arg, Map<String, String> env) throws Exception {
diff --git a/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java b/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
index cad2845..91b5c94 100644
--- a/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
+++ b/knative-build/runtimes/java/core/java8/proxy/src/main/java/org/apache/openwhisk/runtime/java/action/Proxy.java
@@ -41,16 +41,35 @@
 public class Proxy {
     private HttpServer server;
     private JarLoader loader = null;
+    private boolean allowMultipleInits = false;
 
     public Proxy(int port) throws IOException {
         long startTime = Debug.start();
+        Debug.printEnv();
         this.server = HttpServer.create(new InetSocketAddress(port), -1);
         this.server.createContext("/init", new InitHandler());
         this.server.createContext("/run", new RunHandler());
         this.server.setExecutor(null); // creates a default executor
+
+        // Default is false; used primarily for establishing boot shared class cache
+        checkMultipleInitEnabled();
+
         Debug.end(startTime);
     }
 
+    private void checkMultipleInitEnabled() {
+        String strMultipleInit = System.getenv("OW_ALLOW_MULTIPLE_INIT");
+        System.out.printf("OW_ALLOW_MULTIPLE_INIT=%s\n", strMultipleInit);
+
+        // Determine if we allow multiple "init" calls (i.e., Java container reuse); default:false
+        if(strMultipleInit!=null)
+            this.allowMultipleInits = Boolean.parseBoolean(strMultipleInit);
+
+        if(this.allowMultipleInits){
+            System.out.println("Multiple '/init' allowed.");
+        }
+    }
+
     public void start() {
         server.start();
     }
@@ -79,7 +98,8 @@
     private class InitHandler implements HttpHandler {
         public void handle(HttpExchange t) throws IOException {
             long startTime = Debug.start();
-            if (loader != null) {
+
+            if (loader != null && !allowMultipleInits)  {
                 String errorMessage = "Cannot initialize the action more than once.";
                 System.err.println(errorMessage);
                 Proxy.writeError(t, errorMessage);
@@ -108,7 +128,12 @@
 
                         // Start up the custom classloader. This also checks that the
                         // main method exists.
-                        loader = new JarLoader(jarPath, mainClass);
+                        if( loader == null)
+                            loader = new JarLoader(jarPath, mainClass);
+                        else {
+                            loader.addJAR(jarPath);
+                            loader.loadMainClassAndMethod(mainClass);
+                        }
 
                         Proxy.writeResponse(t, 200, "OK");
                         return;
@@ -151,7 +176,8 @@
                 for(Map.Entry<String, JsonElement> entry : entrySet){
                     try {
                         if(!entry.getKey().equalsIgnoreCase("value"))
-                            env.put(String.format("__OW_%s", entry.getKey().toUpperCase()), entry.getValue().getAsString());
+                            env.put(String.format("__OW_%s", entry.getKey().toUpperCase()),
+                                    entry.getValue().getAsString());
                     } catch (Exception e) {}
                 }
 
@@ -169,12 +195,13 @@
                 Proxy.writeResponse(t, 200, output.toString());
                 return;
             } catch (InvocationTargetException ite) {
-                // These are exceptions from the action, wrapped in ite because
-                // of reflection
+                // When you invoke a method using reflection (as we do for the Action function)
+                // and it throws an exception, you must check for it using InvocationTargetException as follows:
                 Throwable underlying = ite.getCause();
                 underlying.printStackTrace(System.err);
                 Proxy.writeError(t,
-                        "An error has occured while invoking the action (see logs for details): " + underlying);
+                        "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);