[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH]: ClassLoader problem in Servlet environment (Tomcat)



Patch Proposal
==============

I've  made changes in LocalDatabase, Env, ClassManager, and OzoneClassLoader to support
passing a parent ClassLoader into LocalDatabase for use by OzoneClassLoader.

I believe this approach is the most flexible solution to the problem at hand.  Please
note that these changes are not well tested and should be reviewed by someone (Falko?)
more familiar with the code than I.

I have this patched version of ozone working well in a Tomcat 3.2.1 environment with no
mention of ozone.jar or my application classes in the system classpath.  (It also works
with ozone.jar in the classpath.)  All jars and classes are contained in the webapp
context.

Now for the bad news.  I now get a ClassCastException when I test this with a
RemoteDatabase.  This doesn't matter to me at the momment, but I image that it would be
a problem for many of you if these changes were commited to CVS!  ;-)  Here's the stack
trace:

    java.lang.ClassCastException: com.javawebguy.smg.db.ozone.NewsImpl
        at org.ozoneDB.core.AbstractObjectContainer.createTarget(AbstractObje
            ctContainer.java:227)
        at org.ozoneDB.core.Transaction.createObject(Transaction.java:395)
        at org.ozoneDB.core.DbRemote.DbCreateObj.perform(DbCreateObj.java:48)
        at org.ozoneDB.core.Transaction.performCommand(Transaction.java:273)
        at org.ozoneDB.core.TransactionManager.performCommand(TransactionMana
            ger.java:366)
        at org.ozoneDB.core.TransactionManager.completeTransaction(Transactio
            nManager.java:334)
        at org.ozoneDB.core.TransactionManager.handleCommand(TransactionManag
            er.java:249)
        at org.ozoneDB.core.InvokeServer.handleClientEvent(InvokeServer.java:
            76)
        at org.ozoneDB.DxLib.net.DxMultiServerClient.run(DxMultiServerClient.
            java:44)
        at java.lang.Thread.run(Thread.java:484)


I would appreciate some help in figuring this one out.

Thanks,
Nathan Probst

Nathan Eric Probst wrote:

> Unfortunately, this didn't fix the problem.
>
> I still get this exception:
> org.ozoneDB.ClassNotFoundExc: com.javawebguy.smg.db.ozone.NewsImpl
>         at org.ozoneDB.ExternalDatabase.sendCommand(ExternalDatabase.java:508)
>         at org.ozoneDB.ExternalDatabase.sendCommand(ExternalDatabase.java:476)
>         at org.ozoneDB.ExternalDatabase.createObject(ExternalDatabase.java:708)
>         at org.ozoneDB.ExternalDatabase.createObject(ExternalDatabase.java:702)
>         at
> com.javawebguy.smg.actions.OzoneNewsAction.perform(OzoneNewsAction.java:99)
>         at
> org.apache.struts.action.ActionServlet.processActionPerform(ActionServlet.java:1620)
>
>         at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1430)
>         at org.apache.struts.action.ActionServlet.doGet(ActionServlet.java:464)
>         at javax.servlet.http.HttpServlet.service(HttpServlet.java:740)
>         at javax.servlet.http.HttpServlet.service(HttpServlet.java:853)
>         at org.apache.tomcat.core.ServletWrapper.doService(ServletWrapper.java:404)
>
>         at org.apache.tomcat.core.Handler.service(Handler.java:286)
>
> So, I assumed that the Thread's ContextClassLoader doesnt' work either.  To test
> this, I used the folloeing code:
>    LocalDatabase db = new LocalDatabase();
>    db.open("/tmp/ozoneDB", 3);
>    System.out.println( "Connected ..." );
>    db.reloadClasses();
>
>    try {
>     System.out.println("Testing `this` class loader...");
>     News foo = (News) this.getClass()
>                           .getClassLoader()
>                           .loadClass(NewsImpl.class.getName())
>                           .newInstance();
>    } catch (ClassNotFoundException e) {
>     e.printStackTrace(System.out);
>    }
>
>    try {
>     System.out.println("Testing Thread class loader...");
>     News bar = (News) Thread.currentThread()
>                             .getContextClassLoader()
>                             .loadClass(NewsImpl.class.getName())
>                             .newInstance();
>    } catch (ClassNotFoundException e) {
>     e.printStackTrace(System.out);
>    }
>
> And received the following as a result:
> Testing `this` class loader...
> Testing Thread class loader...
> java.lang.ClassNotFoundException: com.javawebguy.smg.db.ozone.NewsImpl
>         at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
>         at java.security.AccessController.doPrivileged(Native Method)
>         at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
>         at java.lang.ClassLoader.loadClass(ClassLoader.java:297)
>         at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:286)
>         at java.lang.ClassLoader.loadClass(ClassLoader.java:253)
>         at
> com.javawebguy.smg.actions.OzoneNewsAction.perform(OzoneNewsAction.java:110)
>
> It seems that using the servlet's ClassLoader would work.  Or, better yet, setting
> the servler's ClassLoader as the parent of the OzoneClassLoader, so that when the
> OzoneClassLoader delegates up the chain, the required resources are found.  So, I
> propose that LocalDatabase be modified to accept a ClassLoader in it's constructor
> so that users may optionally choose to specify the parent ClassLoader for the ozone
> Env.
>
> Falko, what do you think?  I've started hacking at this solution, but have had no
> luck yet.  Do you know where changes would need to be made to support this
> behavior?
>
> Thanks,
> Nathan Probst
>
> Falko Braeutigam wrote:
>
> > On Fri, 09 Mar 2001, Falko Braeutigam wrote:
> > > On Fri, 09 Mar 2001, Nathan Eric Probst wrote:
> >   :
> > > > I can make it work with RemoteDatabase by putting my WEB-INF/classes
> > > > directory in the classpath when I start ozone, but this is not a good
> > > > solution.
> > >
> > > There are two ClassLoaders in ozone, one for jdk1.1 and one for jdk1.2+. By
> > > default the 1.1 is used. This one calls Class.forName() to actually get the
> > > class. I'm not sure if Class.forName() uses the threads ClassLoader which is
> > > needed to get the servlet ClassLoader which probably has access to the WEB-INF
> > > classes. I will check this. Stay tuned.
> >
> > I've committed a small patch. ClassManager now uses context loader directly
> > instead of the ozone loader. Thus the ozone server should now have access to
> > all the classes that the servlet engine wants to make available to your servlet
> > (WEB-INF/classes).
> >
> > Falko
> >
> > >
> > >
> > > Falko
> > >
> > > >
> > > > In fact, I need to be able to use only a LocalDatabase and no special
> > > > classpath treatments.  The app I'm developing will be deployed to a
> > > > third-party hosting provider where I will not have control over the
> > > > environment.  So, everything need to work from within the servlet
> > > > context.
> > > >
> > > > Please help!
> > > >
> > > > Thanks,
> > > > Nathan Probst
> > > --
> > > ______________________________________________________________________
> > > Falko Braeutigam                             mailto:falko@smb-tec.com
> > > SMB GmbH                                       http://www.smb-tec.com
> > --
> > ______________________________________________________________________
> > Falko Braeutigam                             mailto:falko@smb-tec.com
> > SMB GmbH                                       http://www.smb-tec.com
Index: org/ozoneDB/LocalDatabase.java
===================================================================
RCS file: /raid/Repository/ozone/org/ozoneDB/LocalDatabase.java,v
retrieving revision 1.31
diff -u -r1.31 LocalDatabase.java
--- org/ozoneDB/LocalDatabase.java	2001/01/26 19:56:06	1.31
+++ org/ozoneDB/LocalDatabase.java	2001/03/10 07:40:25
@@ -28,13 +28,27 @@
  */
 public final class LocalDatabase extends ExternalDatabase {
     
+    private ClassLoader parentClassLoader;
+  
     public Env theEnv;
     
     public String userName;
     
     
     public LocalDatabase() {
+      this((ClassLoader) null);
     }
+
+    /**
+     * Make a new LocalDatabase and pass in a ClassLoader.  For use in 
+     * managed environments such as servlet containers.
+     * 
+     * @param parentClassLoader
+     */
+    public LocalDatabase(ClassLoader parentClassLoader) {
+/*XXX*/ System.out.println("LocalDatabase(ClassLoader): this.parentClassLoader = " + parentClassLoader);
+      this.parentClassLoader = parentClassLoader;
+    }
     
     
     /**
@@ -73,6 +87,7 @@
     
     
     public void open( String _dirName, int _debugLevel ) throws Exception {
+/*XXX*/ System.out.println("LocalDatabase.open(String _dirName, int _debugLevel)");
         String username = System.getProperty( "user.name" );
         open( _dirName, _debugLevel, username, username );
     } 
@@ -100,8 +115,10 @@
             if (_props.get( PROP_DEBUG ) != null) {
                 debugLevel = ((Integer)_props.get( PROP_DEBUG )).intValue();
             } 
-            
-            theEnv = new Env( dirName, debugLevel, false, true );
+/*XXX*/ System.out.println("LocalDatabase.open( Hashtable _props ): " +
+/*XXX*/                    "theEnv = new Env(" + dirName + ", " + debugLevel + ", " + 
+/*XXX*/                    "false, true, " + parentClassLoader + ");");
+            theEnv = new Env( dirName, debugLevel, false, true, parentClassLoader );
             
             // create the user if it doesn't exist yet; in local mode we cannot use
             // the admin tool to do so
Index: org/ozoneDB/core/Env.java
===================================================================
RCS file: /raid/Repository/ozone/org/ozoneDB/core/Env.java,v
retrieving revision 1.77
diff -u -r1.77 Env.java
--- org/ozoneDB/core/Env.java	2001/02/28 10:08:15	1.77
+++ org/ozoneDB/core/Env.java	2001/03/10 07:40:51
@@ -30,35 +30,35 @@
 public final class Env implements Observer {
     
     // constant members ***********************************
-    public final static String		VERSION		= "@version@";
-    public final static String		OS_DIR		= "ostab";
-    public final static String		STATE_FILE 	= "state.properties";
-    public final static String		CONFIG_FILE 	= "config.properties";
-    public final static String		LOG_FILE 	= "log";
-    public final static String		DATA_DIR 	= "data" + File.separator;
-    public final static String		STATS_DIR 	= "stats";
+    public final static String    VERSION   = "@version@";
+    public final static String    OS_DIR    = "ostab";
+    public final static String    STATE_FILE  = "state.properties";
+    public final static String    CONFIG_FILE   = "config.properties";
+    public final static String    LOG_FILE  = "log";
+    public final static String    DATA_DIR  = "data" + File.separator;
+    public final static String    STATS_DIR   = "stats";
     
     /**
      * AdminPort and InvokeServer accepts and admin requests
      */
-    public final static int		ACCEPT_THREAD_PRIORITY = Thread.NORM_PRIORITY + 2;
+    public final static int   ACCEPT_THREAD_PRIORITY = Thread.NORM_PRIORITY + 2;
     
     /**
      * Thread priority of normal transaction.
      */
-    public final static int		TRANSACTION_THREAD_PRIORITY = Thread.NORM_PRIORITY;
+    public final static int   TRANSACTION_THREAD_PRIORITY = Thread.NORM_PRIORITY;
     
-    public final static int		TRANSACTION_MUTEX_PRIORITY = TRANSACTION_THREAD_PRIORITY + 1;
+    public final static int   TRANSACTION_MUTEX_PRIORITY = TRANSACTION_THREAD_PRIORITY + 1;
     
     /**
      * Thread priority deadlock recognition.
      */
-    public final static int		DEADLOCK_THREAD_PRIORITY = Thread.NORM_PRIORITY;
+    public final static int   DEADLOCK_THREAD_PRIORITY = Thread.NORM_PRIORITY;
     
     /**
      * Priority of the server thread (Server.main())
      */
-    public final static int		SERVER_THREAD_PRIORITY = Thread.NORM_PRIORITY + 2;
+    public final static int   SERVER_THREAD_PRIORITY = Thread.NORM_PRIORITY + 2;
     
     
     // class members **************************************
@@ -66,68 +66,73 @@
     /**
      * The one and only ozone environment of this VM.
      */
-    public static Env			theEnv;
+    public static Env     theEnv;
     
     protected static OzoneSecurityManager securityManager;
     
     
     // instance members ***********************************
     
-    public String			dir;
+    public String     dir;
     
     /**
      * Holds the content of the 'state.properties' file. After
      * changing the content the state must be written to disk to make
      * changes persistent.
      */
-    public Setup			state;
+    public Setup      state;
     
     /**
      * Holds the content of the 'config.properties' config file.
      */
-    public Setup			config;
+    public Setup      config;
     
-    public LogWriter 			logWriter = new LogWriter();
+    public LogWriter      logWriter = new LogWriter();
     
     /**
      * This indicates that we are about to shutdown.
      */
-    public boolean			shuttingdown = false;
+    public boolean      shuttingdown = false;
     
-    protected long			idCount = 0;
-    protected long			idBorder = 0;
-    protected long			idRange = 5000;
+    protected long      idCount = 0;
+    protected long      idBorder = 0;
+    protected long      idRange = 5000;
     
     /*
      * The total memory of this VM.
      */
-    protected long			totalMemory;
+    protected long      totalMemory;
 
     /*
      * The amount of memory that should be kept free.
      */
-    protected long			keepMemory;
+    protected long      keepMemory;
     
     /**
      * Interface for the database objects inside the server.
      */
-    public Database			database;
+    public Database     database;
 
     public AdminManager                 adminManager;
     
-    public ClassManager			classManager;
+    public ClassManager     classManager;
     
-    public TransactionManager		transactionManager;
+    public TransactionManager   transactionManager;
     
-    public StoreManager			storeManager;
+    public StoreManager     storeManager;
     
-    public UserManager			userManager;
+    public UserManager      userManager;
     
-    protected InvokeServer		invokeServer;
+    /**
+     * ClassLoader for OzoneClassLoader to set as its parent.
+     */
+    public ClassLoader    parentClassLoader;
+  
+    protected InvokeServer    invokeServer;
     
-    protected DeadlockThread		deadlockThread;
+    protected DeadlockThread    deadlockThread;
     
-    protected DeadlockRecognition	dr;
+    protected DeadlockRecognition dr;
     
     
     // class methods **************************************
@@ -143,10 +148,10 @@
     public static Env currentEnv() {
         return theEnv;
     } 
-    
-    
+
+  
     // instance methods ***********************************
-    
+
     /**
      * Construct a new ozone server environment.
      * 
@@ -155,7 +160,12 @@
      * @param _local
      */
     public Env( String _dirName, int _debugLevel, boolean _verbose, boolean _local ) throws Exception{
-        
+        this( _dirName, _debugLevel, _verbose, _local, null );
+    }
+    
+    
+    public Env( String _dirName, int _debugLevel, boolean _verbose, boolean _local, ClassLoader _parentClassLoader ) throws Exception{
+/*XXX*/ System.out.println("Env( String _dirName, int _debugLevel, boolean _verbose, boolean _local, ClassLoader " + _parentClassLoader + " ) ");
         if (theEnv != null) {
             throw new Exception( "ozone environment (Env) already initialized for this VM" );
         } 
@@ -189,7 +199,11 @@
             calcMemory();
             
             database = new Database( this );
+          
+/*XXX*/ System.out.println("Env(): parentClassLoader = " + _parentClassLoader + ";");
+            parentClassLoader = _parentClassLoader;
             
+/*XXX*/ System.out.println("Env(): classManager = new ClassManager( " + this + " );");
             classManager = new ClassManager( this );
             classManager.startup();
             
@@ -225,8 +239,8 @@
             } 
             throw e;
         } 
-    }
-    
+    }   
+
     
     public void startExternalEventProcessing() throws Exception {
         try {
Index: org/ozoneDB/core/OzoneClassLoader.java
===================================================================
RCS file: /raid/Repository/ozone/org/ozoneDB/core/OzoneClassLoader.java,v
retrieving revision 1.4
diff -u -r1.4 OzoneClassLoader.java
--- org/ozoneDB/core/OzoneClassLoader.java	2001/03/09 13:31:35	1.4
+++ org/ozoneDB/core/OzoneClassLoader.java	2001/03/10 10:07:54
@@ -27,10 +27,18 @@
     
     
     public OzoneClassLoader() {
-        super();
+        this((ClassLoader) null);
+/*XXX*/ System.out.println("OzoneClassLoader(): this((ClassLoader) null);");
+//        super();
+//        flushCache();
+    }
+
+    public OzoneClassLoader(ClassLoader parent) {
+        super(parent);
+/*XXX*/ System.out.println("OzoneClassLoader( ClassLoader ): super(" + parent + ");");
         flushCache();
     }
-    
+
     
     protected void flushCache() {
         classTable = new DxHashMap( 100 );
@@ -38,6 +46,7 @@
     
     
     protected Class findClass( String name ) throws ClassNotFoundException {
+/*XXX*/ System.out.println("OzoneClassLoader.findClass( String )");
         if (name.startsWith( "java" )) {
             throw new ClassNotFoundException( "I'm not here to load system classes." );
         } 
@@ -71,9 +80,10 @@
     public Class loadClass( String name ) throws ClassNotFoundException {
         return loadClass( name, false );
     } 
-    
     
+
     protected Class loadClass( String name, boolean resolve ) throws ClassNotFoundException {
+/*XXX*/ System.out.println("OzoneClassLoader.loadClass( String, boolean )");
         if (name.startsWith( "java" )) {
             return getSystemClassLoader().loadClass( name );
         } else {
@@ -89,7 +99,13 @@
                     
                     // this works around the compatibility problems but causes the reloadClasses()
                     // method to not work
-                    cl = getSystemClassLoader().loadClass( name );
+                    //cl = getSystemClassLoader().loadClass( name );
+                  
+                    // OK, let's try it this way...
+                    // This allows for delegation up the ClassLoader chain.
+/*XXX*/ System.out.println("OzoneClassLoader.loadClass( String, boolean ): " +
+                           "cl = super.loadClass(" + name + ", " + resolve + ");");
+                    cl = super.loadClass(name, resolve);
                 } 
                 classTable.addForKey( cl, name );
             } 
Index: org/ozoneDB/core/ClassManager.java
===================================================================
RCS file: /raid/Repository/ozone/org/ozoneDB/core/ClassManager.java,v
retrieving revision 1.24
diff -u -r1.24 ClassManager.java
--- org/ozoneDB/core/ClassManager.java	2001/03/09 13:31:34	1.24
+++ org/ozoneDB/core/ClassManager.java	2001/03/10 10:08:27
@@ -26,6 +26,7 @@
     
     
     public ClassManager( Env _env ) {
+/*XXX*/ System.out.println("ClassManager( Env )");
         env = _env;
     }
     
@@ -50,8 +51,13 @@
             // not allow to drop classes we can directly use the context loader
             // of the thread to avoid problems in managed environments like
             // servlets
-            Class cl = Thread.currentThread().getContextClassLoader().loadClass( name );
-           // Class cl = classLoader.loadClass( name );
+            //Class cl = Thread.currentThread().getContextClassLoader().loadClass( name );
+/*XXX*/ System.out.println("ClassManager.classForName( String ): " +
+                           "Class cl = classLoader.loadClass(" + name + ");");
+
+            // We can use the OzoneClassLoader as long as we remember to set it's
+            // parentClassLoader through the LocalDatabase( ClassLoader ) constructor.
+            Class cl = classLoader.loadClass( name );
             
             if (env.logWriter.hasTarget( LogWriter.DEBUG3 )) {
                 env.logWriter.newEntry( this, "    class: " + cl.getName() + ", " + cl.hashCode(), LogWriter.DEBUG3 );
@@ -59,6 +65,7 @@
             return cl;
         } 
         catch (ClassNotFoundException e) {
+            e.printStackTrace();
             throw new ClassNotFoundExc( e.getMessage() );
         } 
     } 
@@ -66,7 +73,16 @@
     
     public void dropClasses() throws Exception {
         env.logWriter.newEntry( this, "dropClasses()", LogWriter.DEBUG3 );
-        classLoader = new OzoneClassLoader();
+        // Must call the correct constructor to ensure the correct parent is set.
+        if (env.parentClassLoader != null) {
+/*XXX*/ System.out.println("ClassManager.dropClasses(): " +
+                           "classLoader = new OzoneClassLoader(" + env.parentClassLoader + ");");
+            classLoader = new OzoneClassLoader(env.parentClassLoader);
+        } else {
+/*XXX*/ System.out.println("ClassManager.dropClasses(): " +
+                           "classLoader = new OzoneClassLoader();");
+            classLoader = new OzoneClassLoader();
+        }
     } 
 }