Line data Source code
1 : // Copyright (C) 2013 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.pgm;
16 :
17 : import com.google.common.flogger.FluentLogger;
18 : import com.google.gerrit.launcher.GerritLauncher;
19 : import java.io.File;
20 : import java.io.IOException;
21 : import java.io.InputStream;
22 : import java.lang.reflect.InvocationTargetException;
23 : import java.lang.reflect.Method;
24 : import java.net.URL;
25 : import java.net.URLClassLoader;
26 : import java.util.ArrayList;
27 : import java.util.Properties;
28 :
29 : public class JythonShell {
30 0 : private static final FluentLogger logger = FluentLogger.forEnclosingClass();
31 :
32 : private static final String STARTUP_RESOURCE = "com/google/gerrit/pgm/Startup.py";
33 : private static final String STARTUP_FILE = "Startup.py";
34 :
35 : private Class<?> console;
36 : private Class<?> pyObject;
37 : private Class<?> pySystemState;
38 : private Object shell;
39 : private ArrayList<String> injectedVariables;
40 :
41 0 : public JythonShell() {
42 0 : Properties env = new Properties();
43 : // Let us inspect private class members
44 0 : env.setProperty("python.security.respectJavaAccessibility", "false");
45 :
46 0 : File home = GerritLauncher.getHomeDirectory();
47 0 : if (home != null) {
48 0 : env.setProperty("python.cachedir", new File(home, "jythoncache").getPath());
49 : }
50 :
51 : // For package introspection and "import com.google" to work,
52 : // Jython needs to inspect actual .jar files (not just classloader)
53 0 : StringBuilder classPath = new StringBuilder();
54 0 : final ClassLoader cl = getClass().getClassLoader();
55 0 : if (cl instanceof java.net.URLClassLoader) {
56 : @SuppressWarnings("resource")
57 0 : URLClassLoader ucl = (URLClassLoader) cl;
58 0 : for (URL u : ucl.getURLs()) {
59 0 : if ("file".equals(u.getProtocol())) {
60 0 : if (classPath.length() > 0) {
61 0 : classPath.append(java.io.File.pathSeparatorChar);
62 : }
63 0 : classPath.append(u.getFile());
64 : }
65 : }
66 : }
67 0 : env.setProperty("java.class.path", classPath.toString());
68 :
69 0 : console = findClass("org.python.util.InteractiveConsole");
70 0 : pyObject = findClass("org.python.core.PyObject");
71 0 : pySystemState = findClass("org.python.core.PySystemState");
72 :
73 0 : runMethod(
74 : pySystemState,
75 : pySystemState,
76 : "initialize",
77 : new Class<?>[] {Properties.class, Properties.class},
78 : new Object[] {null, env});
79 :
80 : try {
81 0 : shell = console.getConstructor(new Class<?>[] {}).newInstance();
82 0 : logger.atInfo().log("Jython shell instance created.");
83 0 : } catch (InstantiationException
84 : | IllegalAccessException
85 : | IllegalArgumentException
86 : | InvocationTargetException
87 : | NoSuchMethodException
88 : | SecurityException e) {
89 0 : throw noInterpreter(e);
90 0 : }
91 0 : injectedVariables = new ArrayList<>();
92 0 : set("Shell", this);
93 0 : }
94 :
95 : protected Object runMethod0(
96 : Class<?> klazz, Object instance, String name, Class<?>[] sig, Object[] args)
97 : throws InvocationTargetException {
98 : try {
99 : Method m;
100 0 : m = klazz.getMethod(name, sig);
101 0 : return m.invoke(instance, args);
102 0 : } catch (NoSuchMethodException
103 : | IllegalAccessException
104 : | IllegalArgumentException
105 : | SecurityException e) {
106 0 : throw cannotStart(e);
107 : }
108 : }
109 :
110 : protected Object runMethod(
111 : Class<?> klazz, Object instance, String name, Class<?>[] sig, Object[] args) {
112 : try {
113 0 : return runMethod0(klazz, instance, name, sig, args);
114 0 : } catch (InvocationTargetException e) {
115 0 : throw cannotStart(e);
116 : }
117 : }
118 :
119 : protected Object runInterpreter(String name, Class<?>[] sig, Object[] args) {
120 0 : return runMethod(console, shell, name, sig, args);
121 : }
122 :
123 : protected String getDefaultBanner() {
124 0 : return (String) runInterpreter("getDefaultBanner", new Class<?>[] {}, new Object[] {});
125 : }
126 :
127 : protected void printInjectedVariable(String id) {
128 0 : runInterpreter(
129 : "exec",
130 : new Class<?>[] {String.class},
131 : new Object[] {"print '\"%s\" is \"%s\"' % (\"" + id + "\", " + id + ")"});
132 0 : }
133 :
134 : public void run() {
135 0 : for (String key : injectedVariables) {
136 0 : printInjectedVariable(key);
137 0 : }
138 0 : reload();
139 0 : runInterpreter(
140 : "interact",
141 : new Class<?>[] {String.class, pyObject},
142 : new Object[] {
143 0 : getDefaultBanner()
144 : + " running for Gerrit "
145 0 : + com.google.gerrit.common.Version.getVersion(),
146 : null,
147 : });
148 0 : }
149 :
150 : public void set(String key, Object content) {
151 0 : runInterpreter("set", new Class<?>[] {String.class, Object.class}, new Object[] {key, content});
152 0 : injectedVariables.add(key);
153 0 : }
154 :
155 : private static Class<?> findClass(String klazzname) {
156 : try {
157 0 : return Class.forName(klazzname);
158 0 : } catch (ClassNotFoundException e) {
159 0 : throw noShell("Class " + klazzname + " not found", e);
160 : }
161 : }
162 :
163 : public void reload() {
164 0 : execResource(STARTUP_RESOURCE);
165 0 : execFile(GerritLauncher.getHomeDirectory(), STARTUP_FILE);
166 0 : }
167 :
168 : protected void execResource(String p) {
169 0 : try (InputStream in = JythonShell.class.getClassLoader().getResourceAsStream(p)) {
170 0 : if (in != null) {
171 0 : execStream(in, "resource " + p);
172 : } else {
173 0 : logger.atSevere().log("Cannot load resource %s", p);
174 : }
175 0 : } catch (IOException e) {
176 0 : logger.atSevere().withCause(e).log("%s", e.getMessage());
177 0 : }
178 0 : }
179 :
180 : protected void execFile(File parent, String p) {
181 : try {
182 0 : File script = new File(parent, p);
183 0 : if (script.canExecute()) {
184 0 : runMethod0(
185 : console,
186 : shell,
187 : "execfile",
188 : new Class<?>[] {String.class},
189 0 : new Object[] {script.getAbsolutePath()});
190 : } else {
191 0 : logger.atInfo().log(
192 0 : "User initialization file %s is not found or not executable", script.getAbsolutePath());
193 : }
194 0 : } catch (InvocationTargetException e) {
195 0 : logger.atSevere().withCause(e).log("Exception occurred while loading file %s", p);
196 0 : } catch (SecurityException e) {
197 0 : logger.atSevere().withCause(e).log("SecurityException occurred while loading file %s", p);
198 0 : }
199 0 : }
200 :
201 : protected void execStream(InputStream in, String p) {
202 : try {
203 0 : runMethod0(
204 : console,
205 : shell,
206 : "execfile",
207 : new Class<?>[] {InputStream.class, String.class},
208 : new Object[] {in, p});
209 0 : } catch (InvocationTargetException e) {
210 0 : logger.atSevere().withCause(e).log("Exception occurred while loading %s", p);
211 0 : }
212 0 : }
213 :
214 : private static UnsupportedOperationException noShell(String m, Throwable why) {
215 0 : final String prefix = "Cannot create Jython shell: ";
216 0 : final String postfix = "\n (You might need to install jython.jar in the lib directory)";
217 0 : return new UnsupportedOperationException(prefix + m + postfix, why);
218 : }
219 :
220 : private static UnsupportedOperationException noInterpreter(Throwable why) {
221 0 : final String msg = "Cannot create Python interpreter";
222 0 : return noShell(msg, why);
223 : }
224 :
225 : private static UnsupportedOperationException cannotStart(Throwable why) {
226 0 : final String msg = "Cannot start Jython shell";
227 0 : return new UnsupportedOperationException(msg, why);
228 : }
229 : }
|