Merge "Introduce Gerrit Inspector: interactive Jython shell"
This commit is contained in:
@@ -33,6 +33,7 @@ import com.google.gerrit.pgm.http.jetty.GetUserFilter;
|
||||
import com.google.gerrit.pgm.http.jetty.JettyEnv;
|
||||
import com.google.gerrit.pgm.http.jetty.JettyModule;
|
||||
import com.google.gerrit.pgm.http.jetty.ProjectQoSFilter;
|
||||
import com.google.gerrit.pgm.shell.JythonShell;
|
||||
import com.google.gerrit.pgm.util.ErrorLogFile;
|
||||
import com.google.gerrit.pgm.util.GarbageCollectionLogFile;
|
||||
import com.google.gerrit.pgm.util.LogFileCompressor;
|
||||
@@ -57,6 +58,7 @@ import com.google.gerrit.server.mail.SmtpEmailSender;
|
||||
import com.google.gerrit.server.patch.IntraLineWorkerPool;
|
||||
import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
|
||||
import com.google.gerrit.server.plugins.PluginRestApiModule;
|
||||
import com.google.gerrit.server.schema.DataSourceProvider;
|
||||
import com.google.gerrit.server.schema.SchemaVersionCheck;
|
||||
import com.google.gerrit.server.ssh.NoSshKeyCache;
|
||||
import com.google.gerrit.server.ssh.NoSshModule;
|
||||
@@ -109,6 +111,9 @@ public class Daemon extends SiteProgram {
|
||||
@Option(name = "--console-log", usage = "Log to console (not $site_path/logs)")
|
||||
private boolean consoleLog;
|
||||
|
||||
@Option(name = "-s", usage = "Start interactive shell")
|
||||
private boolean inspector;
|
||||
|
||||
@Option(name = "--run-id", usage = "Cookie to store in $site_path/logs/gerrit.run")
|
||||
private String runId;
|
||||
|
||||
@@ -224,7 +229,15 @@ public class Daemon extends SiteProgram {
|
||||
serverStarted.run();
|
||||
}
|
||||
|
||||
RuntimeShutdown.waitFor();
|
||||
if (inspector) {
|
||||
JythonShell shell = new JythonShell();
|
||||
shell.set("m", manager);
|
||||
shell.set("ds", dbInjector.getInstance(DataSourceProvider.class));
|
||||
shell.set("schk", dbInjector.getInstance(SchemaVersionCheck.class));
|
||||
shell.run();
|
||||
} else {
|
||||
RuntimeShutdown.waitFor();
|
||||
}
|
||||
return 0;
|
||||
} catch (Throwable err) {
|
||||
log.error("Unable to start daemon", err);
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
// 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.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) {
|
||||
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<String>();
|
||||
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) {
|
||||
InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p);
|
||||
if (in != null) {
|
||||
execStream(in, "resource " + p);
|
||||
} else {
|
||||
log.error("Cannot load resource " + p);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user