// 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.shell; import com.google.gerrit.launcher.GerritLauncher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 Logger log = LoggerFactory.getLogger(JythonShell.class); 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"); runMethod(pySystemState, pySystemState, "initialize", new Class[] { Properties.class, Properties.class }, new Object[] { null, env } ); try { shell = console.newInstance(); log.info("Jython shell instance created."); } catch (InstantiationException e) { throw noInterpreter(e); } catch (IllegalAccessException 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 e) { throw cannotStart(e); } catch (SecurityException e) { throw cannotStart(e); } catch (IllegalArgumentException e) { throw cannotStart(e); } catch (IllegalAccessException 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) { runInterpreter("exec", new Class[] { String.class }, new Object[] { "print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")" } ); } public void run() { for (String key : injectedVariables) { printInjectedVariable(key); } reload(); 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) { 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(final String p) { try (InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p)) { if (in != null) { execStream(in, "resource " + p); } else { log.error("Cannot load resource " + p); } } catch (IOException e) { log.error(e.getMessage(), e); } } protected void execFile(final File parent, final String p) { try { File script = new File(parent, p); if (script.canExecute()) { runMethod0(console, shell, "execfile", new Class[] { String.class }, new Object[] { script.getAbsolutePath() } ); } else { log.info("User initialization file " + script.getAbsolutePath() + " is not found or not executable"); } } catch (InvocationTargetException e) { log.error("Exception occured while loading file " + p + " : ", e); } catch (SecurityException e) { log.error("SecurityException occured while loading file " + p + " : ", e); } } protected void execStream(final InputStream in, final String p) { try { runMethod0(console, shell, "execfile", new Class[] { InputStream.class, String.class }, new Object[] { in, p } ); } catch (InvocationTargetException e) { log.error("Exception occured while loading " + p + " : ", e); } } private static UnsupportedOperationException noShell(final 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); } }