| // Copyright (C) 2013 The Android Open Source Project |
| // |
| // Licensed 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 com.google.gerrit.pgm; |
| |
| import com.google.common.flogger.FluentLogger; |
| import com.google.gerrit.launcher.GerritLauncher; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.Properties; |
| |
| public class JythonShell { |
| private static final FluentLogger logger = FluentLogger.forEnclosingClass(); |
| |
| private static final String STARTUP_RESOURCE = "com/google/gerrit/pgm/Startup.py"; |
| private static final String STARTUP_FILE = "Startup.py"; |
| |
| private Class<?> console; |
| private Class<?> pyObject; |
| private Class<?> pySystemState; |
| private Object shell; |
| private ArrayList<String> injectedVariables; |
| |
| public JythonShell() { |
| Properties env = new Properties(); |
| // Let us inspect private class members |
| env.setProperty("python.security.respectJavaAccessibility", "false"); |
| |
| File home = GerritLauncher.getHomeDirectory(); |
| if (home != null) { |
| env.setProperty("python.cachedir", new File(home, "jythoncache").getPath()); |
| } |
| |
| // For package introspection and "import com.google" to work, |
| // Jython needs to inspect actual .jar files (not just classloader) |
| StringBuilder classPath = new StringBuilder(); |
| final ClassLoader cl = getClass().getClassLoader(); |
| if (cl instanceof java.net.URLClassLoader) { |
| @SuppressWarnings("resource") |
| URLClassLoader ucl = (URLClassLoader) cl; |
| for (URL u : ucl.getURLs()) { |
| if ("file".equals(u.getProtocol())) { |
| if (classPath.length() > 0) { |
| classPath.append(java.io.File.pathSeparatorChar); |
| } |
| classPath.append(u.getFile()); |
| } |
| } |
| } |
| env.setProperty("java.class.path", classPath.toString()); |
| |
| console = findClass("org.python.util.InteractiveConsole"); |
| pyObject = findClass("org.python.core.PyObject"); |
| pySystemState = findClass("org.python.core.PySystemState"); |
| |
| @SuppressWarnings("unused") |
| var unused = |
| runMethod( |
| pySystemState, |
| pySystemState, |
| "initialize", |
| new Class<?>[] {Properties.class, Properties.class}, |
| new Object[] {null, env}); |
| |
| try { |
| shell = console.getConstructor(new Class<?>[] {}).newInstance(); |
| logger.atInfo().log("Jython shell instance created."); |
| } catch (InstantiationException |
| | IllegalAccessException |
| | IllegalArgumentException |
| | InvocationTargetException |
| | NoSuchMethodException |
| | SecurityException e) { |
| throw noInterpreter(e); |
| } |
| injectedVariables = new ArrayList<>(); |
| set("Shell", this); |
| } |
| |
| protected Object runMethod0( |
| Class<?> klazz, Object instance, String name, Class<?>[] sig, Object[] args) |
| throws InvocationTargetException { |
| try { |
| Method m; |
| m = klazz.getMethod(name, sig); |
| return m.invoke(instance, args); |
| } catch (NoSuchMethodException |
| | IllegalAccessException |
| | IllegalArgumentException |
| | SecurityException e) { |
| throw cannotStart(e); |
| } |
| } |
| |
| protected Object runMethod( |
| Class<?> klazz, Object instance, String name, Class<?>[] sig, Object[] args) { |
| try { |
| return runMethod0(klazz, instance, name, sig, args); |
| } catch (InvocationTargetException e) { |
| throw cannotStart(e); |
| } |
| } |
| |
| protected Object runInterpreter(String name, Class<?>[] sig, Object[] args) { |
| return runMethod(console, shell, name, sig, args); |
| } |
| |
| protected String getDefaultBanner() { |
| return (String) runInterpreter("getDefaultBanner", new Class<?>[] {}, new Object[] {}); |
| } |
| |
| protected void printInjectedVariable(String id) { |
| @SuppressWarnings("unused") |
| var unused = |
| runInterpreter( |
| "exec", |
| new Class<?>[] {String.class}, |
| new Object[] {"print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")"}); |
| } |
| |
| public void run() { |
| for (String key : injectedVariables) { |
| printInjectedVariable(key); |
| } |
| reload(); |
| |
| @SuppressWarnings("unused") |
| var unused = |
| runInterpreter( |
| "interact", |
| new Class<?>[] {String.class, pyObject}, |
| new Object[] { |
| getDefaultBanner() |
| + " running for Gerrit " |
| + com.google.gerrit.common.Version.getVersion(), |
| null, |
| }); |
| } |
| |
| public void set(String key, Object content) { |
| @SuppressWarnings("unused") |
| var unused = |
| runInterpreter( |
| "set", new Class<?>[] {String.class, Object.class}, new Object[] {key, content}); |
| |
| injectedVariables.add(key); |
| } |
| |
| private static Class<?> findClass(String klazzname) { |
| try { |
| return Class.forName(klazzname); |
| } catch (ClassNotFoundException e) { |
| throw noShell("Class " + klazzname + " not found", e); |
| } |
| } |
| |
| public void reload() { |
| execResource(STARTUP_RESOURCE); |
| execFile(GerritLauncher.getHomeDirectory(), STARTUP_FILE); |
| } |
| |
| protected void execResource(String p) { |
| try (InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p)) { |
| if (in != null) { |
| execStream(in, "resource " + p); |
| } else { |
| logger.atSevere().log("Cannot load resource %s", p); |
| } |
| } catch (IOException e) { |
| logger.atSevere().withCause(e).log("%s", e.getMessage()); |
| } |
| } |
| |
| protected void execFile(File parent, String p) { |
| try { |
| File script = new File(parent, p); |
| if (script.canExecute()) { |
| @SuppressWarnings("unused") |
| var unused = |
| runMethod0( |
| console, |
| shell, |
| "execfile", |
| new Class<?>[] {String.class}, |
| new Object[] {script.getAbsolutePath()}); |
| } else { |
| logger.atInfo().log( |
| "User initialization file %s is not found or not executable", script.getAbsolutePath()); |
| } |
| } catch (InvocationTargetException e) { |
| logger.atSevere().withCause(e).log("Exception occurred while loading file %s", p); |
| } catch (SecurityException e) { |
| logger.atSevere().withCause(e).log("SecurityException occurred while loading file %s", p); |
| } |
| } |
| |
| protected void execStream(InputStream in, String p) { |
| try { |
| @SuppressWarnings("unused") |
| var unused = |
| runMethod0( |
| console, |
| shell, |
| "execfile", |
| new Class<?>[] {InputStream.class, String.class}, |
| new Object[] {in, p}); |
| } catch (InvocationTargetException e) { |
| logger.atSevere().withCause(e).log("Exception occurred while loading %s", p); |
| } |
| } |
| |
| private static UnsupportedOperationException noShell(String m, Throwable why) { |
| final String prefix = "Cannot create Jython shell: "; |
| final String postfix = "\n (You might need to install jython.jar in the lib directory)"; |
| return new UnsupportedOperationException(prefix + m + postfix, why); |
| } |
| |
| private static UnsupportedOperationException noInterpreter(Throwable why) { |
| final String msg = "Cannot create Python interpreter"; |
| return noShell(msg, why); |
| } |
| |
| private static UnsupportedOperationException cannotStart(Throwable why) { |
| final String msg = "Cannot start Jython shell"; |
| return new UnsupportedOperationException(msg, why); |
| } |
| } |