adding JARs to sys.path at runtime

submitted by: Steve Langer

Introduction

During Oct-Nov 2006 there was a thread in the jython-users group titled "adding JARs to sys.path". More accurately the objective there was to add JARs to the sys.path at runtime. Several people asked the question, "Why would you want to do that?" Well there are at least 2 good reasons. First, if you want to distribute a jython or Java package that includes non-standard Jars in it. Perhaps you want to make life easier for the target user and not demand that they know how to set environment variables. A second even more compelling reason is when there is no normal user account to provide environment variables.

"What?", you ask. Well, in my case I came upon this problem in the following way. I am working on an open source IHE Image Archive Actor [1] and needed a web interface. I'm using AJAX on the client side to route database calls through CGI to a jython-JDBC enabled API. Testing the jython-JDBC API from the command line worked fine, I had the PostGres driver in my CLASSPATH. But, when called via the web interface I got "zxJDBC error, postGres driver not found" errors. Why? Because APACHE was calling the API and APACHE is not a normal account with environment variables.

What to do?

The jython-users thread had many suggestions but none were found to work. For books, Chapter 11 of O'Reilly's "Jython Essentials" [2] mentions under "System and File Modules" that "... to load a class at runtime one also needs an appropriate class loader." Of course, no mention is made beyond that. After a while, it occured to me that perhaps someone in the Java world had found a similar problem and had solved it. Then all that would be required is to translate that solution. And that is exactly what happened.

Method

For brevity we will not repeat the original Java code here, but it will be linked to in the References [3]. This is how I call the Jython class (note that one can use either addFile or addURL depending on whether the Jar is on a locally accesable file system or remote server).

import sys
from com.ziclix.python.sql import zxJDBC

d,u,p,v = "jdbc:postgresql://localhost/img_arc2","postgres","","org.postgresql.Driver"

try :
    # if called from command line with .login CLASSPATH setup right,this works
    db = zxJDBC.connect(d, u, p, v)
except:
    # if called from Apache or account where the .login has not set CLASSPATH
    # need to use run-time CLASSPATH Hacker
    try :
        jarLoad = classPathHacker()
        a = jarLoad.addFile("/usr/share/java/postgresql-jdbc3.jar")
        db = zxJDBC.connect(d, u, p, v)
    except :
        sys.exit ("still failed \n%s" % (sys.exc_info() ))

And here is the class "classPathHacker" which is what the original author called his solution. In fact, you can simply Google on "classPathHacker" to find the Java solution.

class classPathHacker :
##########################################################
# from http://forum.java.sun.com/thread.jspa?threadID=300557
#
# Author: SG Langer Jan 2007 translated the above Java to this
#       Jython class
# Purpose: Allow runtime additions of new Class/jars either from
#       local files or URL
######################################################
        import java.lang.reflect.Method
        import java.io.File
        import java.net.URL
        import java.net.URLClassLoader
        import jarray

        def addFile (self, s):
        #############################################
        # Purpose: If adding a file/jar call this first
        #       with s = path_to_jar
        #############################################
                module = "utils:classPathHacker: addFile"

                # make a URL out of 's'
                f = self.java.io.File (s)
                u = f.toURL ()
                a = self.addURL (u)
                return a

        def addURL (self, u):
        ##################################
        # Purpose: Call this with u= URL for
        #       the new Class/jar to be loaded
        #################################
                module = "utils:classPathHacker: addURL"

                parameters = self.jarray.array([self.java.net.URL], self.java.lang.Class)
                sysloader =  self.java.lang.ClassLoader.getSystemClassLoader()
                sysclass = self.java.net.URLClassLoader
                method = sysclass.getDeclaredMethod("addURL", parameters)
                a = method.setAccessible(1)
                jar_a = self.jarray.array([u], self.java.lang.Object)
                b = method.invoke(sysloader, jar_a)
                return u

Conclusions

That's it. Depressingly short for what it does, but then that's another proof of the power of this language. I hope you find this as powerful and useful as I have. It allows the possibility of distributing jython packages with all their file dependencies within the installation directory, freeing the user or developer from the need to alter user environment variables, which should lead to more programmer control and thus higher reliabliity.

References

1. Information on IHE, http://www.ihe.net/Resources/index.cfm

2. Samuele Pedroni, Noel Rappin. (2002). "Jython Essentials", O'Reilly Publishing, http://www.ora.com

3. The original Java classPathHacker, http://forum.java.sun.com/thread.jspa?threadID=300557

JythonMonthly/Articles/January2007/3 (last edited 2009-04-10 10:28:36 by AndrewKuchling)