/*
JythonEditObjectFactory.java
This class acts as a factory for Jython-based DBEditObjects that are
loaded from an external location (disk, HTTP, FTP, etc).
Created: 5 August 2004
Module By: Deepak Giridharagopal <deepak@arlut.utexas.edu>
-----------------------------------------------------------------------
Ganymede Directory Management System
Copyright (C) 1996-2010
The University of Texas at Austin
Contact information
Author Email: ganymede_author@arlut.utexas.edu
Email mailing list: ganymede@arlut.utexas.edu
US Mail:
Computer Science Division
Applied Research Laboratories
The University of Texas at Austin
PO Box 8029, Austin TX 78713-8029
Telephone: (512) 835-3200
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package arlut.csd.ganymede.server;
import org.python.core.PyException;
import org.python.core.PySystemState;
import org.python.util.PythonInterpreter;
import arlut.csd.ganymede.common.Invid;
/**
* <p>
* This class is a factory for Jython-based DBEditObject subclasses. It will
* attempt to load the source for these subclasses from an external location
* that is specified in the options of the given
* {@link arlut.csd.ganymede.server.DBObjectBase DBObjectBase}.
* </p>
*
* <p>
* It implements the 3 required factory methods that are analogues of the 3
* constructors for all {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}
* constructors.
* </p>
*/
public class JythonEditObjectFactory {
/**
* The Jython interpreter that's responsible for loading the external source file
* and instantiating a Java class from it.
*/
static PythonInterpreter interp = null;
/**
* <p>
* Factory version of the {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}
* customization constructor.
* </p>
*
* @param base the object base the newly created DBEditObject should be attached to
*/
public static DBEditObject factory(DBObjectBase base)
{
return factory(base, null, null, null);
}
/**
* <p>
* Factory version of the {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}
* new object constructor.
* </p>
*
* @param base the object base the newly created DBEditObject should be attached to
* @param invid the Invid to associate with this object
* @param editset the transaction to associate the object with
*/
public static DBEditObject factory(DBObjectBase base, Invid invid, DBEditSet editset)
{
return factory(base, invid, editset, null);
}
/**
* <p>
* Factory version of the {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}
* check-out constructor.
* </p>
*
* @param original the object to be "checked out"
* @param editset the transaction to associate the object with
*/
public static DBEditObject factory(DBObject original, DBEditSet editset)
{
return factory(null, null, editset, original);
}
/**
* <p>
* This is the <i>workhorse</i> factory method that the constructors all end up
* calling in the end. This function will simply pass all of the possible
* constructor parameters to the Jython-based
* {@link arlut.csd.ganymede.server.DBEditObject DBEditObject}, whose constructor
* is smart enough to determine which superclass constructor to call.
* </p>
*
* <p>
* This should never be called directly...use one of the other 3 public factory
* methods instead.
* </p>
*/
private static DBEditObject factory(DBObjectBase base, Invid invid, DBEditSet editset, DBObject original)
{
if (interp == null)
{
Ganymede.debug("Initializing interpreter");
initializeInterpreter();
}
String uri = null;
/* If we've been handed a DBObjectBase, use it to find the URI (which
* is held in the DBObjectBase's options string). If we don't have one,
* the we must have been called from the copy constructor and we should
* have a non-null DBObject. We can get its parent DBObjectBase and then
* get the options string from there.
*/
if (base != null)
{
uri = base.getClassOptionString();
}
else if (original != null)
{
uri = original.getBase().getClassOptionString();
}
return loadJythonClass(uri, base, invid, editset, original);
}
/**
* <p>
* Sets up the Jython interpreter and ensures that it can find any requisite
* external libraries
* </p>
*/
private synchronized static void initializeInterpreter()
{
PySystemState.initialize();
interp = new PythonInterpreter(null, new PySystemState());
try
{
/* Import the additional Jython library routines */
interp.exec("import sys");
interp.exec("sys.path.append( sys.prefix + '" + System.getProperty("file.separator") + "' + 'jython-lib.jar' )");
}
catch (PyException pex)
{
throw new RuntimeException(pex.toString());
}
}
/**
* <p>
* This method takes the given URI and transforms the Jython text it points to into
* a Java object. All of the other arguments as simply passed to the Jython text's
* constructor.
* </p>
*
* <p>
* This will only load the Jython text once...after that it's cached. To unload
* the uri's class definition, use the unloadURI method.
* </p>
*
* @return a newly constructed DBEditObject subclass
*/
private synchronized static DBEditObject loadJythonClass(String uri, DBObjectBase base, Invid invid, DBEditSet editset, DBObject original)
{
if (uri == null || uri.equals(""))
{
Ganymede.debug("No URI was passed in!");
return null;
}
try
{
Ganymede.debug("Invoking Jython loader");
interp.exec("from JythonEditObjectBootstrapper import *");
/* We'll go ahead an pass in all of the args we've got, null or not. The
* Jython class' constructor is smart enough to figure out which superclass
* constructor to call based on which of the arguments are null. */
interp.set("uri", uri);
interp.set("base", base);
interp.set("invid", invid);
interp.set("editset", editset);
interp.set("original", original);
interp.exec("obj = get_jythonEditObject(uri, base, invid, editset, original)");
return (DBEditObject) interp.get("obj", DBEditObject.class);
}
catch (PyException pex)
{
Ganymede.debug(pex.toString());
throw new GanymedeManagementException(pex.toString());
}
}
/**
* <p>
* Removes the class definition associated with the given URI from the
* class cache. This method should only be used to designate that the
* next time the given URI is loaded, you should bypass the cache.
* </p>
*
* @param uri the uri we want to reset. If this is <b>null</b>, then
* <b>ALL</b> of the class definitions in the cache will be unloaded
*/
public synchronized static void unloadURI(String uri)
{
if (interp == null)
{
return;
}
interp.set("uri_to_unload", uri);
interp.exec("unload_class(uri_to_unload)");
}
/**
* <p>
* Similar to {@link arlut.csd.ganymede.server.JythonEditObjectFactory#unloadURI(java.lang.String) unloadURI},
* except this method will unload <b>ALL</b> of the class definitions
* in the cache.
* </p>
*/
public synchronized static void unloadAllURIs()
{
unloadURI(null);
}
}