costin 2005/09/28 23:35:48 Modified: util/loader/org/apache/tomcat/util/loader Loader.java Module.java ModuleClassLoader.java ModuleListener.java Repository.java package.html Removed: util/loader/org/apache/tomcat/util/loader LoaderProperties.java Log: AFAIK this is not currently used, it's just an experimental refactoring of the loading I started long time ago. Besides beeing useable standalone and more generic, it supports few extra features, like reloading of .jar files and using a 'flat' loader, similar with jboss and jmx. - refactoring to clean up the interfaces ( code is based on catalina loader ) - add support for delegating to a parent classloader, needed for the case this is embeded in another app - fix various things. Revision Changes Path 1.3 +396 -36 jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/Loader.java Index: Loader.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/Loader.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- Loader.java 6 Oct 2004 15:41:44 -0000 1.2 +++ Loader.java 29 Sep 2005 06:35:47 -0000 1.3 @@ -19,18 +19,24 @@ import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.Hashtable; +import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; /** - * Boostrap loader for Catalina. This application constructs a class loader + * Boostrap loader for Catalina or other java apps. + * + * This application constructs a class loader * for use in loading the Catalina internal classes (by accumulating all of the * JAR files found in the "server" directory under "catalina.home"), and * starts the regular execution of the container. The purpose of this @@ -38,20 +44,44 @@ * other classes they depend on, such as an XML parser) out of the system * class path and therefore not visible to application level classes. * + * + * Merged with CatalinaProperties: + * Load a properties file describing the modules and startup sequence. + * This is responsible for configuration of the loader. + * TODO: support jmx-style configuration, including persistence. + * TODO: better separate legacy config and the new style + * + * The properties file will be named "loader.properties" or + * "catalina.properties" ( for backwad compatibility ) and + * will be searched in: + * - TODO + * + * Properties used: + * - TODO + * + * loader.* and *.loader properties are used internally by the loader ( + * *.loader is for backward compat with catalina ). + * All other properties in the config file are set as System properties. + * + * Based on o.a.catalina.bootstrap.CatalinaProperties - utility class to read + * the bootstrap Catalina configuration. + + * * @author Craig R. McClanahan * @author Remy Maucherat * @author Costin Manolache */ public final class Loader { - private static final boolean DEBUG=true; //LoaderProperties.getProperty("loader.Loader.debug") != null; + private static final boolean DEBUG=true; //LoaderProperties.getProperty("loader.debug.Loader") != null; + // If flat, only one loader is created. If false - one loader per jar/dir private static final boolean FLAT=false;//LoaderProperties.getProperty("loader.Loader.flat") != null; // -------------------------------------------------------------- Constants - protected static final String CATALINA_HOME_TOKEN = "${catalina.home}"; - protected static final String CATALINA_BASE_TOKEN = "${catalina.base}"; + private static final String CATALINA_HOME_TOKEN = "${catalina.home}"; + private static final String CATALINA_BASE_TOKEN = "${catalina.base}"; // ------------------------------------------------------- Static Variables @@ -75,24 +105,53 @@ protected ClassLoader catalinaLoader = null; private String[] args; private Hashtable repositories=new Hashtable(); + private ClassLoader parentClassLoader; + private static Properties properties = null; + + private static String propFile; // -------------------------------------------------------- Private Methods + + /** Set the parent class loader - can be used instead of setParent, + * in case this is the top loader and needs to delagate to embedding app. + * The common loader will delegate to this loader + * + * @param myL + */ + public void setParentClassLoader(ClassLoader myL) { + this.parentClassLoader=myL; + } - private void initClassLoaders() { + + /** Initialize the loader, creating all repositories. + * Will create common, server, shared. + * + * TODO: create additional repos. + * + */ + public void init() { try { - commonRepository = initRepository("common", null); - catalinaRepository = initRepository("server", commonRepository); + commonRepository = initRepository("common", null, parentClassLoader); + catalinaRepository = initRepository("server", commonRepository,null); catalinaLoader = catalinaRepository.getClassLoader(); - sharedRepository = initRepository("shared", commonRepository); + sharedRepository = initRepository("shared", commonRepository,null); } catch (Throwable t) { log("Class loader creation threw exception", t); System.exit(1); } } + /** Create a new repository. + * No Module is added ( currently ) + * TODO: use props to prepopulate, if any is present. + * + * @param name + * @param parent + * @return + */ public Repository createRepository(String name, Repository parent) { Repository lg=new Repository(this); @@ -120,13 +179,15 @@ * @return * @throws Exception */ - private Repository initRepository(String name, Repository parent) + private Repository initRepository(String name, Repository parent, ClassLoader pcl) throws Exception { - String value = LoaderProperties.getProperty(name + ".loader"); + String value = getProperty(name + ".loader"); Repository lg=createRepository(name, parent ); - if( DEBUG ) log( "Creating loading group " + name + " - " + value); + if( pcl != null ) + lg.setParentClassLoader( pcl ); + if( DEBUG ) log( "Creating loading group " + name + " - " + value + " " + pcl); if ((value == null) || (value.equals(""))) return lg; @@ -142,11 +203,12 @@ // Local repository boolean packed = false; + if (repository.startsWith(CATALINA_HOME_TOKEN)) { - repository = LoaderProperties.getCatalinaHome() + repository = getCatalinaHome() + repository.substring(CATALINA_HOME_TOKEN.length()); } else if (repository.startsWith(CATALINA_BASE_TOKEN)) { - repository = LoaderProperties.getCatalinaBase() + repository = getCatalinaBase() + repository.substring(CATALINA_BASE_TOKEN.length()); } @@ -198,14 +260,17 @@ * file and removed from args[]. */ private void processCLI() { - if( args.length > 0 && + if( args!=null && args.length > 0 && (args[0].toLowerCase().endsWith(".tomcat") || + args[0].toLowerCase().endsWith(".loader") || args[0].toLowerCase().endsWith("loader.properties") )) { String props=args[0]; String args2[]=new String[args.length-1]; System.arraycopy(args, 1, args2, 0, args2.length); args=args2; - LoaderProperties.setPropertiesFile(props); + setPropertiesFile(props); + } else { + loadProperties(); } } @@ -222,22 +287,26 @@ processCLI(); // Set Catalina path - LoaderProperties.setCatalinaHome(); - LoaderProperties.setCatalinaBase(); + setCatalinaHome(); + setCatalinaBase(); - initClassLoaders(); + init(); Thread.currentThread().setContextClassLoader(catalinaLoader); securityPreload(catalinaLoader); + autostart(); + } + + private void autostart() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { // Load our startup classes and call its process() method /* Why multiple classes ? * - maybe you want to start more "servers" ( tomcat ,something else ) * - easy hook for other load on startup modules ( like a jmx hook ) * - maybe split the loader-specific code from catalina */ - String startupClasses=LoaderProperties.getProperty("loader.auto-startup", + String startupClasses=getProperty("loader.auto-startup", "org.apache.catalina.startup.CatalinaModuleListener"); Vector v=split( startupClasses ); @@ -261,6 +330,8 @@ // all arg processing moved there. Maybe we can make it consistent // for all startup schemes listener.start(); + } else if ( startupInstance instanceof Runnable ) { + ((Runnable)startupInstance).run(); } else { Class paramTypes[] = new Class[0]; Object paramValues[] = new Object[0]; @@ -268,12 +339,21 @@ startupInstance.getClass().getMethod("execute", paramTypes); if( method==null ) method = startupInstance.getClass().getMethod("start", paramTypes); - method.invoke(startupInstance, paramValues); + if( method!=null ) + method.invoke(startupInstance, paramValues); } } } + /** Returns one of the repositories. + * + * Typically at startup we create at least: "common", "shared" and "server", with + * same meaning as in tomcat. + * + * @param name + * @return + */ public Repository getRepository( String name ) { return (Repository)repositories.get(name); } @@ -285,7 +365,7 @@ return; } - String value=LoaderProperties.getProperty("security.preload"); + String value=getProperty("security.preload"); Vector repo=split( value ); Enumeration elems=repo.elements(); while (elems.hasMoreElements()) { @@ -298,8 +378,11 @@ } } + // ----------------------------------------------------------- Main Program + /** Access to the command line arguments, when Loader is used to launc an app. + */ public String[] getArgs() { return args; } @@ -329,16 +412,287 @@ } - public void setCatalinaHome(String s) { + + /** + * Initialize the loader properties explicitely. + * + * TODO: add setPropertiesRes + * + * @param props + */ + public void setPropertiesFile(String props) { + propFile=props; + loadProperties(); + } + + /** + * Return specified property value. + */ + static String getProperty(String name) { + if( properties==null ) loadProperties(); + return properties.getProperty(name); + } + + + /** + * Return specified property value. + */ + static String getProperty(String name, String defaultValue) { + if( properties==null ) loadProperties(); + return properties.getProperty(name, defaultValue); + } + + /** + * Load properties. + * Will try: + * - "catalina.config" system property ( a URL ) + * - "catalina.base", "catalina.home", "user.dir" system properties + + * "/conf/" "../conf" "/" + "loader.properties" or "catalina.properties" + * - /org/apache/catalina/startup/catalina.properties + * + * Properties will be loaded as system properties. + * + * loader.properties was added to allow coexistence with bootstrap.jar ( the + * current scheme ), since the classpaths are slightly different. + */ + static void loadProperties() { + properties = new Properties(); + + InputStream is = null; + Throwable error = null; + + // TODO: paste the code to do ${} substitution + // TODO: add the code to detect where tomcat-properties is loaded from + if( propFile != null ) { + try { + File properties = new File(propFile); + is = new FileInputStream(properties); + if( is!=null && DEBUG ) { + log("Loaded from loader.properties " + properties ); + } + } catch( Throwable t) { + System.err.println("Can't find " + propFile); + return; + } + } + + if( is == null ) { + try { + // "catalina.config" system property + String configUrl = System.getProperty("catalina.config"); + if (configUrl != null) { + is = (new URL(configUrl)).openStream(); + if( is!=null && DEBUG ) { + log("Loaded from catalina.config " + configUrl ); + } + } + } catch (Throwable t) { + // Ignore + } + } + + if( is == null ) { + try { + // "loader.config" system property + String configUrl = System.getProperty("loader.config"); + if (configUrl != null) { + is = (new URL(configUrl)).openStream(); + if( is!=null && DEBUG ) { + log("Loaded from catalina.config " + configUrl ); + } + } + } catch (Throwable t) { + // Ignore + } + } + + if (is == null) { + try { + setCatalinaBase(); // use system properties, then user.dir + File home = new File(getCatalinaBase()); + File conf = new File(home, "conf"); + + // use conf if exists, or the base directory otherwise + if( ! conf.exists() ) conf = new File(home, "../conf"); + if( ! conf.exists() ) conf = home; + File propertiesF=null; + if( conf.exists() ) + propertiesF= new File(conf, "loader.properties"); + if( ! propertiesF.exists() ) { + propertiesF= new File( home, "loader.properties"); + } + if( propertiesF.exists() ) + is = new FileInputStream(propertiesF); + if( is!=null && DEBUG ) { + log("Loaded from loader.properties " + properties ); + } + } catch (Throwable t) { + // Ignore + } + } + + if (is == null) { + try { + File home = new File(getCatalinaBase()); + File conf = new File(home, "conf"); + File properties = new File(conf, "catalina.properties"); + is = new FileInputStream(properties); + if( is!=null && DEBUG ) { + log("Loaded from catalina.properties " + properties ); + } + } catch (Throwable t) { + // Ignore + } + } + + if (is == null) { + try { + is = Loader.class.getResourceAsStream + ("/org/apache/catalina/startup/catalina.properties"); + if( is!=null && DEBUG ) { + log("Loaded from o/a/c/startup/catalina.properties " ); + } + + } catch (Throwable t) { + // Ignore + } + } + + if (is == null) { + try { + is = Loader.class.getResourceAsStream + ("loader.properties"); + if( is!=null && DEBUG ) { + log("Loaded from res loader.properties " ); + } + } catch (Throwable t) { + // Ignore + } + } + + if (is != null) { + try { + properties.load(is); + is.close(); + } catch (Throwable t) { + error = t; + } + } + +// if ((is == null) || (error != null)) { +// // Do something +// log("Error: no properties found !!!"); +// } + + // Register the _unused_ properties as system properties + if( properties != null ) { + Enumeration enumeration = properties.propertyNames(); + while (enumeration.hasMoreElements()) { + String name = (String) enumeration.nextElement(); + String value = properties.getProperty(name); + if( "security.preload".equals( name )) continue; + if( "package.access".equals( name )) continue; + if( "package.definition".equals( name )) continue; + if( name.endsWith(".loader")) continue; + if( name.startsWith("loader.")) continue; + if (value != null) { + System.setProperty(name, value); + } + } + } + + } + + static void setCatalinaHome(String s) { System.setProperty( "catalina.home", s ); } - public void setCatalinaBase(String s) { + static void setCatalinaBase(String s) { System.setProperty( "catalina.base", s ); } + + /** + * Get the value of the catalina.home environment variable. + * + * @deprecated + */ + static String getCatalinaHome() { + if( properties==null ) loadProperties(); + return System.getProperty("catalina.home", + System.getProperty("user.dir")); + } + + + /** + * Get the value of the catalina.base environment variable. + * + * @deprecated + */ + static String getCatalinaBase() { + if( properties==null ) loadProperties(); + return System.getProperty("catalina.base", getCatalinaHome()); + } + + + /** + * Set the <code>catalina.base</code> System property to the current + * working directory if it has not been set. + */ + static void setCatalinaBase() { + if( properties==null ) loadProperties(); + + if (System.getProperty("catalina.base") != null) + return; + if (System.getProperty("catalina.home") != null) + System.setProperty("catalina.base", + System.getProperty("catalina.home")); + else + System.setProperty("catalina.base", + System.getProperty("user.dir")); + + } + + + /** + * Set the <code>catalina.home</code> System property to the current + * working directory if it has not been set. + */ + static void setCatalinaHome() { + + if (System.getProperty("catalina.home") != null) + return; + File bootstrapJar = + new File(System.getProperty("user.dir"), "bootstrap.jar"); + File tloaderJar = + new File(System.getProperty("user.dir"), "tomcat-loader.jar"); + if (bootstrapJar.exists() || tloaderJar.exists()) { + try { + System.setProperty + ("catalina.home", + (new File(System.getProperty("user.dir"), "..")) + .getCanonicalPath()); + } catch (Exception e) { + // Ignore + System.setProperty("catalina.home", + System.getProperty("user.dir")); + } + } else { + System.setProperty("catalina.home", + System.getProperty("user.dir")); + } + + } + + + /** - * Public as ModuleClassLoader. + * Get the module from the classloader. Works only for classloaders created by + * this package - or extending ModuleClassLoader. + * + * This shold be the only public method that allows this - Loader acts as a + * guard, only if you have the loader instance you can access the internals. + * * * @param cl * @return @@ -387,16 +741,18 @@ log(" Not found: "+ file.getAbsolutePath()); continue; } +// String cPath=file.getCanonicalPath(); +// URL url=null; +// +// if( cPath.toLowerCase().endsWith(".jar") || +// cPath.toLowerCase().endsWith(".zip") ) { +// url = new URL("file", null, cPath); +// } else { +// url = new URL("file", null, cPath + File.separator); +// } + URL url=file.toURL(); if (DEBUG) - sb.append(" "+ file.getAbsolutePath()); - String cPath=file.getCanonicalPath(); - URL url=null; - if( cPath.toLowerCase().endsWith(".jar") || - cPath.toLowerCase().endsWith(".zip") ) { - url = new URL("file", null, cPath); - } else { - url = new URL("file", null, cPath + File.separator); - } + sb.append(" : "+ url); if( ! FLAT ) { addLoader(lg, parent, new URL[] { url }); } else { @@ -421,10 +777,14 @@ if (!filename.endsWith(".jar")) continue; File file = new File(directory, filenames[j]); +// if (DEBUG) +// sb.append(" [pak]="+ file.getCanonicalPath()); +// URL url = new URL("file", null, +// file.getCanonicalPath()); + URL url=file.toURL(); if (DEBUG) - sb.append(" "+ file.getAbsolutePath()); - URL url = new URL("file", null, - file.getCanonicalPath()); + sb.append(" pk="+ url); + if( ! FLAT ) { addLoader(lg, parent, new URL[] { url }); } else { 1.3 +35 -4 jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/Module.java Index: Module.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/Module.java,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- Module.java 6 Oct 2004 15:41:44 -0000 1.2 +++ Module.java 29 Sep 2005 06:35:47 -0000 1.3 @@ -21,17 +21,16 @@ import java.io.File; import java.io.Serializable; import java.lang.reflect.Constructor; +import java.net.MalformedURLException; import java.net.URL; // Based on org.apache.catalina.Loader - removed most of the catalina-specific /** - * Base representation for "server extensions" ( connectors, realms, etc ), webapps, - * libraries. - * + * Represent one unit of code - jar, webapp, etc. Modules can be reloaded independently, + * and may be part of a flat structure or a hierarchy. * * @author Costin Manolache - * * @author Craig R. McClanahan * @author Remy Maucherat */ @@ -220,6 +219,16 @@ // ------------------------------------------------------- Private Methods + /** + * Experiment for basic lifecycle driven by higher layer. + * start() and stop() methods will be called on the class when the + * module is stopped and started. + * + */ + //public void addModuleClass(String s) { + + //} + /** Set the class used to construct the class loader. * * The alternative is to set the context class loader to allow loaderClass @@ -262,6 +271,7 @@ } else { classLoader=new ModuleClassLoader( classpath, parentClassLoader); } + System.err.println("---- Created class loader " + classpath + " " + parentClassLoader + " repo=" + repository.getName() + " " + parent); classLoader.setModule(this); classLoader.setDelegate( delegate ); @@ -286,6 +296,22 @@ public void setClasspath(URL[] array) { this.classpath=array; } + + /** Set the path to the module. + * In normal use, each module will be associated with one jar or + * classpath dir. + * + * @param name + */ + public void setPath(String name) { + this.classpath=new URL[1]; + try { + classpath[0]=new URL(name); + } catch (MalformedURLException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } /** @@ -334,4 +360,9 @@ } + public void setParentClassLoader(ClassLoader parentClassLoader2) { + this.parentClassLoader=parentClassLoader2; + } + + } 1.2 +59 -20 jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/ModuleClassLoader.java Index: ModuleClassLoader.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/ModuleClassLoader.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ModuleClassLoader.java 6 Oct 2004 05:00:04 -0000 1.1 +++ ModuleClassLoader.java 29 Sep 2005 06:35:47 -0000 1.2 @@ -68,19 +68,23 @@ // Don't use commons logging or configs to debug loading - logging is dependent // on loaders and drags a lot of stuff in the classpath // - private static final boolean DEBUG=false; //LoaderProperties.getProperty("loader.ModuleClassLoader.debug") != null; - private static final boolean DEBUGNF=false;//LoaderProperties.getProperty("loader.ModuleClassLoader.debugNF") != null; + private static final boolean DEBUG=false; //LoaderProperties.getProperty("loader.debug.ModuleClassLoader") != null; + private static final boolean DEBUGNF=false;//LoaderProperties.getProperty("loader.debug.ModuleClassLoaderNF") != null; // ----------------------------------------------------------- Constructors public ModuleClassLoader(URL repositories[], ClassLoader parent) { super(repositories, parent); + if(DEBUG) log( "NEW ModuleClassLoader " + parent + " " + repositories.length); + updateStamp(); } public ModuleClassLoader(URL repositories[]) { super(repositories); + if(DEBUG) log( "NEW ModuleClassLoader -null-"+ " " + repositories.length); + updateStamp(); } @@ -104,6 +108,8 @@ */ protected long lastJarAccessed = 0L; + protected long lastModified=0L; + /** * Has this component been started? */ @@ -113,17 +119,6 @@ // ------------------------------------------------------------- Properties - - /** - * Return the "delegate first" flag for this class loader. - */ -// boolean getDelegate() { -// -// return (this.delegate); -// -// } - - /** * Set the "delegate first" flag for this class loader. * @@ -168,8 +163,13 @@ void addRepository(String repository) { // Add this repository to our underlying class loader try { + boolean mod=modified(); URL url = new URL(repository); super.addURL(url); + if( ! mod ) { + // don't check if it is modified, so it works + updateStamp(); + } } catch (MalformedURLException e) { IllegalArgumentException iae = new IllegalArgumentException ("Invalid repository: " + repository); @@ -179,6 +179,30 @@ } } + void updateStamp() { + URL cp[]=super.getURLs(); + if (cp != null ) { + for (int i = 0; i <cp.length; i++) { + File f=new File(cp[i].getFile()); + long lm=f.lastModified(); + if( lm > lastModified ) lastModified=lm; + } + } + } + + private boolean dirCheck(File dir ) { + log("Checking " + dir ); + File subd[]=dir.listFiles(); + for( int i=0; i< subd.length; i++ ) { + long lm=subd[i].lastModified(); + if( lm > lastModified ) return true; + if( subd[i].isDirectory() ) { + if( dirCheck(subd[i]) ) return true; + } + } + return false; + } + /** * Have one or more classes or resources been modified so that a reload * is appropriate? @@ -186,6 +210,19 @@ * Not public - call it via Module */ boolean modified() { + URL cp[]=super.getURLs(); + if (cp != null ) { + for (int i = 0; i <cp.length; i++) { + File f=new File(cp[i].getFile()); + long lm=f.lastModified(); + if( lm > lastModified ) return true; + // assume dirs are used only for debug and small + if( f.isDirectory() ) { + if( dirCheck(f) ) return true; + } + } + } + if (DEBUG) log("modified() false"); @@ -215,8 +252,9 @@ log("findClass() -->RuntimeException " + name, e); throw e; } catch( ClassNotFoundException ex ) { + URL cp[]=this.getURLs(); if (DEBUGNF) - log("findClass() NOTFOUND " + name); + log("findClass() NOTFOUND " + name + " " + (( cp.length > 0 ) ? cp[0].toString() : "") ); throw ex; } @@ -364,7 +402,7 @@ } - // to avoid duplication + // to avoid duplication - get resource from parent, when delegating private URL getResourceParentDelegate(String name) { URL url=null; ClassLoader loader = getParent(); @@ -384,6 +422,7 @@ return (url); } } + if( DEBUG ) log("getResource not found by parent " + loader); return url; } @@ -476,8 +515,8 @@ try { clazz = findClass(name); if (clazz != null) { -// if (DEBUG) -// log("loadClass - FOUND findClass " + delegate + " " + name + " , " + resolve); + if (DEBUG) + log("loadClass - FOUND findClass " + delegate + " " + name + " , " + resolve); if (resolve) resolveClass(clazz); return (clazz); } @@ -515,7 +554,7 @@ } } - if( DEBUGNF ) log("loadClass(): NOTFOUND " + name ); + if( DEBUGNF ) log("loadClass(): NOTFOUND " + name + " xxx " + getParent() + " " + repository.getName() ); throw new ClassNotFoundException(name); } @@ -583,10 +622,10 @@ // ------------------ Local methods ------------------------ private void log(String s ) { - System.err.println("ModuleCL: " + s); + System.err.println("ModuleClassLoader: " + s); } private void log(String s, Throwable t ) { - System.err.println("ModuleCL: " + s); + System.err.println("ModuleClassLoader: " + s); t.printStackTrace(); } 1.2 +5 -0 jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/ModuleListener.java Index: ModuleListener.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/ModuleListener.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- ModuleListener.java 6 Oct 2004 05:00:04 -0000 1.1 +++ ModuleListener.java 29 Sep 2005 06:35:47 -0000 1.2 @@ -17,6 +17,11 @@ package org.apache.tomcat.util.loader; +/** + * Interface providing notifications on Module events. + * + * @author Costin Manolache + */ public interface ModuleListener { /** Called when a module group is created. This is only called when a new group 1.2 +90 -31 jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/Repository.java Index: Repository.java =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/Repository.java,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- Repository.java 6 Oct 2004 05:00:04 -0000 1.1 +++ Repository.java 29 Sep 2005 06:35:47 -0000 1.2 @@ -46,23 +46,26 @@ */ public class Repository { - private static final boolean DEBUG=true; //LoaderProperties.getProperty("loader.Repository.debug") != null; + private static final boolean DEBUG=Loader.getProperty("loader.debug.Repository") != null; // Allows the (experimental) use of jar indexes // Right now ( for small set of jars, incomplete build ) it's a tiny 3.5 -> 3.4 sec dif. - private static final boolean USE_IDX=true;// //LoaderProperties.getProperty("loader.Repository.useIdx") != null; + private static final boolean USE_IDX=Loader.getProperty("loader.Repository.noIndex") == null; private Vector loaders=new Vector(); private String name; private Vector grpModules=new Vector(); private transient Loader loader; - private Repository parent; - private transient ModuleClassLoader groupClassLoader; private Hashtable prefixes=new Hashtable(); - public Repository() { + // For delegation + private ClassLoader parentClassLoader; + private Repository parent; + + + private Repository() { } public Repository(Loader loader) { @@ -74,13 +77,16 @@ return loader; } - public void addModule( Module mod ) { + void addModule( Module mod ) { mod.setRepository( this ); grpModules.addElement(mod); if( loader.listener!=null ) { loader.listener.moduleAdd(mod); } + + if( parentClassLoader != null ) + mod.setParentClassLoader( parentClassLoader ); if(! mod.isStarted()) { mod.start(); @@ -92,6 +98,8 @@ try { if( USE_IDX ) { processJarIndex(mod); + // TODO: if we are in the initial starting, write cache only once + // TODO: write it only if there is a change in the timestamp writeCacheIdx(); } } catch (Exception e) { @@ -101,11 +109,34 @@ } + public void newModule( String path ) { + Module m=new Module(); + m.setPath( path ); + addModule( m ); + } + public Enumeration getModules() { return grpModules.elements(); } - public Repository getParent() { + public void checkReload() { + try { + Enumeration mE=grpModules.elements(); + while( mE.hasMoreElements() ) { + Module m=(Module)mE.nextElement(); + log("Modified " + m + " " + m.modified()); + + if( m.modified() ) { + m.stop(); + m.start(); + } + } + } catch( Throwable t ) { + t.printStackTrace(); + } + } + + Repository getParent() { return parent; } @@ -116,6 +147,16 @@ public void setParent(Repository parent) { this.parent = parent; } + + /** Set the parent class loader - can be used instead of setParent, + * in case this is the top loader and needs to delagate to embedding app + * + * @param myL + */ + public void setParentClassLoader(ClassLoader myL) { + this.parentClassLoader=myL; + } + /** Add a class loder to the group. * @@ -144,6 +185,7 @@ loaders.removeElement(cl); if(DEBUG) log("removed " + loaders.size() + "/" + oldSize + ": " + cl); + // TODO: remove from index } /** Return a class loader associated with the group. @@ -153,16 +195,27 @@ */ public ClassLoader getClassLoader() { if( groupClassLoader==null ) { - if( parent == null ) { + ClassLoader pcl=parentClassLoader; + if( pcl==null && parent!=null ) { + pcl=parent.getClassLoader(); + } + if( pcl==null ) { + pcl=Thread.currentThread().getContextClassLoader(); + } + + if( pcl == null ) { + // allow delegation to embedding app groupClassLoader=new ModuleClassLoader(new URL[0]); } else { - groupClassLoader=new ModuleClassLoader(new URL[0], parent.getClassLoader()); + groupClassLoader=new ModuleClassLoader(new URL[0], pcl); } + if( DEBUG ) log("---------- Created repository loader " + pcl ); groupClassLoader.start(); groupClassLoader.setRepository(this); } return groupClassLoader; } + /** * Find a class in the group. It'll iterate over each loader * and try to find the class - using only the method that @@ -173,29 +226,17 @@ * @param classN * @return */ - public Class findClass(ClassLoader caller, String classN ) { + Class findClass(ClassLoader caller, String classN ) { Class clazz=null; // do we have it in index ? if( USE_IDX ) { - int lastIdx=classN.lastIndexOf("."); - String prefix=(lastIdx>0) ? classN.substring(0, lastIdx) : classN; - Object mO=prefixes.get(prefix.replace('.', '/')); - if( mO!=null ) { - if( mO instanceof Module ) { - Module m=(Module)mO; - try { - Class c=((ModuleClassLoader)m.getClassLoader()).findLocalClass(classN); - //log("Prefix: " +prefix + " " + classN + " " + m); - return c; - } catch (Exception e) { - //log("Prefix err: " +prefix + " " + classN + " " + m + " " + e); - //return null; - } - } else { - Module mA[]=(Module[])mO; - for( int i=0; i<mA.length; i++ ) { - Module m=mA[i]; + int lastIdx=classN.lastIndexOf("."); + String prefix=(lastIdx>0) ? classN.substring(0, lastIdx) : classN; + Object mO=prefixes.get(prefix.replace('.', '/')); + if( mO!=null ) { + if( mO instanceof Module ) { + Module m=(Module)mO; try { Class c=((ModuleClassLoader)m.getClassLoader()).findLocalClass(classN); //log("Prefix: " +prefix + " " + classN + " " + m); @@ -204,10 +245,22 @@ //log("Prefix err: " +prefix + " " + classN + " " + m + " " + e); //return null; } + } else { + Module mA[]=(Module[])mO; + for( int i=0; i<mA.length; i++ ) { + Module m=mA[i]; + try { + Class c=((ModuleClassLoader)m.getClassLoader()).findLocalClass(classN); + //log("Prefix: " +prefix + " " + classN + " " + m); + return c; + } catch (Exception e) { + //log("Prefix err: " +prefix + " " + classN + " " + m + " " + e); + //return null; + } + } } } } - } // TODO: move the vector to a [] for( int i=loaders.size()-1; i>=0; i-- ) { @@ -243,9 +296,9 @@ * @param name2 * @return */ - public URL findResource(ModuleClassLoader caller, String classN) { + URL findResource(ModuleClassLoader caller, String classN) { URL url=null; - + if( DEBUG ) log("Repository.findResource " + classN + " " + caller ); for( int i=loaders.size()-1; i>=0; i-- ) { // TODO: for regular CL, just use loadClass, they'll not recurse // The behavior for non-SCL or not in the group loader is the same as for parent loader @@ -380,6 +433,12 @@ private void writeCacheIdx() throws IOException { + // For each module we write the timestamp, filename then the index + // The idea is to load this single file to avoid scanning many jars + + // we'll use the cache + } + } 1.2 +26 -23 jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/package.html Index: package.html =================================================================== RCS file: /home/cvs/jakarta-tomcat-connectors/util/loader/org/apache/tomcat/util/loader/package.html,v retrieving revision 1.1 retrieving revision 1.2 diff -u -r1.1 -r1.2 --- package.html 6 Oct 2004 05:00:04 -0000 1.1 +++ package.html 29 Sep 2005 06:35:47 -0000 1.2 @@ -1,29 +1,32 @@ <html> <body> -The goal of this package is to provide class loading functionality, -similar in behavior with Jboss and MLET loaders. There -is no policy, just a mechanism - how it is used depends on the higher level. - -This package only deals with class loading, so it has to minimize the dependencies. -Currently there is no dependency except bare JDK1.3. Debug logging is minimal and done -using System.err. - -There is no JMX depenency - the modules and loaders can be registered with JMX -by a module using the ModuleListener, after jmx class loader is created. -TODO: conditional loading of jmx.jar, parser based on jdk version. - -Configuration uses a simple properties file describing the classpaths and the -classes to launch - i.e. all a class loader needs to know, and -similar with the old catalina.properties. - -To implement a good module system on top of this we need lifecycle ( already present -in tomcat ) and discipline in making sure there are no stale references to -objects in a module after its death. An OSGI-like system may seem to -deal with the second problem - but it doesn't solve anything, it just makes -the references more visible and requires major changes in how you code, as -well as rewriting of most apis and implementations - and in the end it still -doesn't solve the problem. +The goal of this package is to provide class loading functionality, similar in behavior with Jboss and MLET loaders. There +is no specific policy, just a mechanism - how it is used depends on the application. It is based on the tomcat5.x class +loader, with additional support for the 'repository' delegation. + +The main class is Loader - it controls a hierarchy of Repositories, each consisting of one or more Modules. Each Module corresponds to one jar file +or directory - and will have a ModuleClassLoader that answers only for that file. The Repository is associated with a ModuleClassLoader that delegates to +each Module. It is possible to add/remove/replace Modules at runtime - just like in JMX and JBoss. In normal tomcat, only webapps can be reloaded - this also allow connectors, valves, and any internal server jar to be reloaded. + +The package only deals with class loading, with minimal the dependencies. Currently there is no dependency except bare JDK1.3. + +The modules and loaders can be registered with JMX by a module using the ModuleListener, after jmx class loader is created. Note that JMX is not a dependency and doesn't have to be in the classpath - it can be loaded in a Repository, and then something like Modeler will do the mapping. + +Configuration uses a simple properties file describing the classpaths and the classes to launch - i.e. all a class loader needs to know, and similar with the old catalina.properties. + +To implement a good module system on top of this we need lifecycle ( already present in tomcat ) and discipline in making sure there are no stale references to objects in a module after its death. + +An OSGI-like system may seem to deal with the second problem - but it doesn't solve anything, it just makes +the references more visible and requires major changes in how you code, as well as rewriting of most apis and implementations - and in the end it still +doesn't solve the problem. JBoss and JMX are actually on the right track in this, as oposed to OSGI. + +The loader is also trying to stick to the minimal classloading-related functionality - unlike OSGI wich is reinventing all weels. I started working on the loader after trying to see how OSGI would fit, and realizing that it is a wrong design. + + +<h2>Using loader for launching</h2> + +Loader has a main(), and will look up the loader.properties file, create the class loaders, and then launch any 'auto-startup' classes. The must important part of launching an app is setting the classpath, and using Loader allows the app to use more advanced features than using simple CLASSPATH. </body> </html> \ No newline at end of file
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]