From 55dbb1ff9f638aecdce19111cdb1831e23f7f1e0 Mon Sep 17 00:00:00 2001 From: Charlie Groves Date: Wed, 21 Jan 2009 23:32:55 +0000 Subject: [PATCH] Add a jython initializer service. If META-INF/services/org.python.core.JythonInitializer is on the classpath, the class named in that file will be instantiated and used in Jython's initialization. This is useful when Jython is initialized by a library outside of your control, but some customization still needs to be done to Jython's environment. I promise not to add a JythonFactory or a JythonFactoryFactory. --- Lib/test/check_for_initializer_in_syspath.py | 2 + Lib/test/test_jython_initializer.py | 17 +++ build.xml | 10 ++ src/org/python/core/JythonInitializer.java | 35 +++++ src/org/python/core/PySystemState.java | 126 +++++++++++++++++- src/org/python/core/ThreadStateMapping.java | 5 +- .../org.python.core.JythonInitializer | 1 + .../SyspathAppendingInitializer.java | 18 +++ 8 files changed, 206 insertions(+), 8 deletions(-) create mode 100644 Lib/test/check_for_initializer_in_syspath.py create mode 100644 Lib/test/test_jython_initializer.py create mode 100644 src/org/python/core/JythonInitializer.java create mode 100644 tests/data/initializer/META-INF/services/org.python.core.JythonInitializer create mode 100644 tests/data/initializer/SyspathAppendingInitializer.java diff --git a/Lib/test/check_for_initializer_in_syspath.py b/Lib/test/check_for_initializer_in_syspath.py new file mode 100644 index 000000000..3423d0c69 --- /dev/null +++ b/Lib/test/check_for_initializer_in_syspath.py @@ -0,0 +1,2 @@ +import sys +assert "/from_SyspathAppendingInitializer_with_love" in sys.path diff --git a/Lib/test/test_jython_initializer.py b/Lib/test/test_jython_initializer.py new file mode 100644 index 000000000..03f048838 --- /dev/null +++ b/Lib/test/test_jython_initializer.py @@ -0,0 +1,17 @@ +import unittest +import subprocess +import sys +from test import test_support + +class TestUsingInitializer(unittest.TestCase): + def test_syspath_initializer(self): + fn = test_support.findfile("check_for_initializer_in_syspath.py") + ret = subprocess.Popen([sys.executable, fn], + env={"CLASSPATH":"tests/data/initializer"}).wait() + self.assertEquals(0, ret) + +def test_main(): + test_support.run_unittest(TestUsingInitializer) + +if __name__ == "__main__": + test_main() diff --git a/build.xml b/build.xml index 9a9178259..d9732b41b 100644 --- a/build.xml +++ b/build.xml @@ -482,6 +482,16 @@ The readme text for the next release will be like: nowarn="${nowarn}"> + + + + Java + Service Providers + */ +public interface JythonInitializer { + + /** + * Called from {@link PySystemState#initialize} with the full set of initialization arguments. + * Implementations may modify or replace the given arguments, and must call + * {@link PySystemState#doInitialize}. + * + * @param argv + * - The command line arguments the jython interpreter was started with, or an empty + * array if jython wasn't started directly from the command line. + * @param classLoader + * - The classloader to be used by sys, or null if no sys-specific classloader was + * specified + */ + void initialize(Properties preProperties, + Properties postProperties, + String[] argv, + ClassLoader classLoader, + ExtensiblePyObjectAdapter adapter); +} diff --git a/src/org/python/core/PySystemState.java b/src/org/python/core/PySystemState.java index ffad09aee..bd253d371 100644 --- a/src/org/python/core/PySystemState.java +++ b/src/org/python/core/PySystemState.java @@ -1,11 +1,16 @@ // Copyright (c) Corporation for National Research Initiatives package org.python.core; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URL; import java.net.URLDecoder; +import java.nio.charset.Charset; +import java.nio.charset.UnsupportedCharsetException; import java.security.AccessControlException; import java.util.Map; import java.util.Properties; @@ -539,9 +544,6 @@ private static void initRegistry(Properties preProperties, Properties postProper Py.writeError("systemState", "trying to reinitialize registry"); return; } - if (preProperties == null) { - preProperties = getBaseProperties(); - } registry = preProperties; String prefix = findRoot(preProperties, postProperties, jarFileName); @@ -572,9 +574,7 @@ private static void initRegistry(Properties preProperties, Properties postProper } } catch (SecurityException e) { } - if (postProperties != null) { - registry.putAll(postProperties); - } + registry.putAll(postProperties); if (standalone) { // set default standalone property (if not yet set) if (!registry.containsKey(PYTHON_CACHEDIR_SKIP)) { @@ -642,6 +642,116 @@ public static synchronized void initialize(Properties preProperties, if (initialized) { return; } + if (preProperties == null) { + preProperties = getBaseProperties(); + } + if (postProperties == null) { + postProperties = new Properties(); + } + try { + ClassLoader context = Thread.currentThread().getContextClassLoader(); + if (context != null) { + if (initialize(preProperties, postProperties, argv, classLoader, adapter, context)) { + return; + } + } else { + Py.writeDebug("initializer", "Context class loader null, skipping"); + } + ClassLoader sysStateLoader = PySystemState.class.getClassLoader(); + if (sysStateLoader != null) { + if (initialize(preProperties, + postProperties, + argv, + classLoader, + adapter, + sysStateLoader)) { + return; + } + } else { + Py.writeDebug("initializer", "PySystemState.class class loader null, skipping"); + } + } catch (UnsupportedCharsetException e) { + Py.writeWarning("initializer", "Unable to load the UTF-8 charset to read an initializer definition"); + e.printStackTrace(System.err); + } catch (SecurityException e) { + // Must be running in a security environment that doesn't allow access to the class + // loader + } catch (Exception e) { + Py.writeWarning("initializer", + "Unexpected exception thrown while trying to use initializer service"); + e.printStackTrace(System.err); + } + doInitialize(preProperties, postProperties, argv, classLoader, adapter); + } + + private static final String INITIALIZER_SERVICE = + "META-INF/services/org.python.core.JythonInitializer"; + + /** + * Attempts to read a SystemStateInitializer service from the given classloader, instantiate it, + * and initialize with it. + * + * @throws UnsupportedCharsetException + * if unable to load UTF-8 to read a service definition + * @return true if a service is found and successfully initializes. + */ + private static boolean initialize(Properties pre, + Properties post, + String[] argv, + ClassLoader sysClassLoader, + ExtensiblePyObjectAdapter adapter, + ClassLoader initializerClassLoader) { + InputStream in = initializerClassLoader.getResourceAsStream(INITIALIZER_SERVICE); + if (in == null) { + Py.writeDebug("initializer", "'" + INITIALIZER_SERVICE + "' not found on " + initializerClassLoader); + return false; + } + BufferedReader r = new BufferedReader(new InputStreamReader(in, Charset.forName("UTF-8"))); + String className; + try { + className = r.readLine(); + } catch (IOException e) { + Py.writeWarning("initializer", "Failed reading '" + INITIALIZER_SERVICE + "' from " + + initializerClassLoader); + e.printStackTrace(System.err); + return false; + } + Class initializer; + try { + initializer = initializerClassLoader.loadClass(className); + } catch (ClassNotFoundException e) { + Py.writeWarning("initializer", "Specified initializer class '" + className + + "' not found, continuing"); + return false; + } + try { + ((JythonInitializer)initializer.newInstance()).initialize(pre, + post, + argv, + sysClassLoader, + adapter); + } catch (Exception e) { + Py.writeWarning("initializer", "Failed initializing with class '" + className + + "', continuing"); + e.printStackTrace(System.err); + return false; + } + if (!initialized) { + Py.writeWarning("initializer", "Initializer '" + className + + "' failed to call doInitialize, using default initialization"); + } + return initialized; + } + + + public static synchronized PySystemState doInitialize(Properties preProperties, + Properties postProperties, + String[] argv, + ClassLoader classLoader, + ExtensiblePyObjectAdapter adapter) { + if (initialized) { + return Py.defaultSystemState; + } initialized = true; Py.setAdapter(adapter); boolean standalone = false; @@ -663,11 +773,13 @@ public static synchronized void initialize(Properties preProperties, // Finish up standard Python initialization... Py.defaultSystemState = new PySystemState(); Py.setSystemState(Py.defaultSystemState); - if (classLoader != null) + if (classLoader != null) { Py.defaultSystemState.setClassLoader(classLoader); + } Py.initClassExceptions(getDefaultBuiltins()); // Make sure that Exception classes have been loaded new PySyntaxError("", 1, 1, "", ""); + return Py.defaultSystemState; } private static void initStaticFields() { diff --git a/src/org/python/core/ThreadStateMapping.java b/src/org/python/core/ThreadStateMapping.java index e605df8c7..8c470950e 100644 --- a/src/org/python/core/ThreadStateMapping.java +++ b/src/org/python/core/ThreadStateMapping.java @@ -9,10 +9,13 @@ public ThreadState getThreadState(PySystemState newSystemState) { return ts; } - Thread t = Thread.currentThread(); + Thread t = Thread.currentThread(); if (newSystemState == null) { Py.writeDebug("threadstate", "no current system state"); + if (Py.defaultSystemState == null) { + PySystemState.initialize(); + } newSystemState = Py.defaultSystemState; } diff --git a/tests/data/initializer/META-INF/services/org.python.core.JythonInitializer b/tests/data/initializer/META-INF/services/org.python.core.JythonInitializer new file mode 100644 index 000000000..fa58d98b4 --- /dev/null +++ b/tests/data/initializer/META-INF/services/org.python.core.JythonInitializer @@ -0,0 +1 @@ +SyspathAppendingInitializer diff --git a/tests/data/initializer/SyspathAppendingInitializer.java b/tests/data/initializer/SyspathAppendingInitializer.java new file mode 100644 index 000000000..e0326ef37 --- /dev/null +++ b/tests/data/initializer/SyspathAppendingInitializer.java @@ -0,0 +1,18 @@ +import java.util.Properties; +import org.python.core.JythonInitializer; +import org.python.core.Py; +import org.python.core.PySystemState; +import org.python.core.adapter.ExtensiblePyObjectAdapter; + +public class SyspathAppendingInitializer implements JythonInitializer { + public void initialize(Properties preProperties, + Properties postProperties, + String[] argv, + ClassLoader classLoader, + ExtensiblePyObjectAdapter adapter) { + postProperties.put(PySystemState.PYTHON_CACHEDIR_SKIP, "true"); + PySystemState defaultState = + PySystemState.doInitialize(preProperties, postProperties, argv, classLoader, adapter); + defaultState.path.append(Py.newString("/from_SyspathAppendingInitializer_with_love")); + } +}