The following comment has been added to this issue:
Author: Ate Douma
Created: Thu, 27 May 2004 8:11 AM
Body:
I've done some more research and experimenting.
First remark: There is no such thing as a cross-context classloader within Tomcat.
For detailed information about Tomcat (5) classloaders see:
http://jakarta.apache.org/tomcat/tomcat-5.0-doc/class-loader-howto.html
The bad news second: Merging the J2 web-app ContextClassLoader with the
ContextClassLoader of a portlet web app ourselves will not work with Tomcat.
I created a MergedClassLoader doing just that which even works.
The problem though, is that when a RequestDispatcher forwards or includes to another
resource (servlet, jsp etc.) Tomcat again sets its own ContextClassLoader. This we
cannot intercept (and thus not solve) without hacking the Tomcat core.
Even classworlds won't be able to help us out here.
Our problems really are caused by the way classes are loaded dynamically and the
(formally correct) ContextClassLoader Tomcat provides for each web app.
When a web app A (e.. portlet app) wants to dynamically instantiate a new class which
is local to web app B (e.g. Jetspeed), it needs the classloader of web app B to do so.
This can easily be done by using the classloader from an already instantiated class
from web app B: Class.forName("className",instanceFromWebAppB.getClassLoader());
What clearly won't work is: instanceFromWebAppA.getClass().forName("className").
Our current problems are caused by the way OJB dynamically creates new class
instances: Class.forName("className", currentThread.getContextClassLoader()).
The only way we can get this to work is temporarily setting the correct
ContextClassLoader in every method where OJB is accessed like this:
ClassLoader cl = Thread.currentThread().getContextClassLoader();
try
{
Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
// OJB dynamic class instantiating method call
}
finally
{
Thread.currentThread().setContextClassLoader(cl);
}
This alone already is a lot of work. but worse, its not enough!
What if the above code returns a lazy/proxied Collection or Iterator. Only when their
elements are accessed the instantiation will take place resulting again in a
ClassNotFoundException.
There is no protection against these type of constructs (other then never lazy loading
or a proxy)!
Another, more successful, solution would be to patch OJB to use
Class.forName("className") instead. Note: the OJB jar *must* be in the J2 web-app lib
after that. Hacking external libraries isn't my favorite solution though.
The simplest solution in theory is of course moving the OJB jar, and all classes it
needs to load, to the tomcat/shared/lib folder. In practice, this requires moving all
the J2 classes and all the jars (but one) to the tomcat/shared folder because almost
everything is interrelated to each other (at runtime that is).
I've tested this out.
The only problem I encountered was getting a connection to the Oracle database because
commons-dbcp couldn't find the Oracle driver classes (which are in my case also in
tomcat/shared/lib). Very strange. After moving only the commons-dbcp jar back into the
Jetspeed web-app lib folder everything worked perfectly. No more
ClassNotFoundExceptions at all.
Personally I don't like this solution. All of J2 is shared that way. There really
isn't a need for a commons jar anymore because of this. But, we can improve this by
only moving all objects which can be dynamically instantiated by OJB to the
tomcat/shared folder, including the OJB jar.
One thing which remains important, even after we have fixed all this, is being very
careful with providing access to objects which might be instantiated dynamically using
the currentThread contextClassLoader.
Its not very likely only OJB is doing that...
---------------------------------------------------------------------
View this comment:
http://issues.apache.org/jira/browse/JS2-56?page=comments#action_35747
---------------------------------------------------------------------
View the issue:
http://issues.apache.org/jira/browse/JS2-56
Here is an overview of the issue:
---------------------------------------------------------------------
Key: JS2-56
Summary: Objects Fail to Create with Tomcat Classloaders and cross context
Type: Task
Status: Open
Priority: Major
Project: Jetspeed 2
Components:
Components Core
Fix Fors:
2.0-dev/cvs
Versions:
2.0-a1
Assignee: David Sean Taylor
Reporter: David Sean Taylor
Created: Tue, 25 May 2004 9:35 PM
Updated: Thu, 27 May 2004 8:11 AM
Environment: Tomcat 4 and 5
Description:
J2 uses a cross-context class loader to share objects created in the jetspeed context
with other portlet application contexts. This works fine when objects have already
been instantiated in the jetspeed context.
J2 infuses a common servlet into every portlet application that is deployed into a
Tomcat application server via the J2 portal's PAM (Portlet Application Manager). The
code that runs in this servlet is placed in Tomcat's shared/lib directory so that both
Jetspeed common servlet and the Jetspeed portal can share objects.
The problems I am seeing with this approach are rooting in the creation of new
objects. For example, if a portlet application, such as the HW_App, has a portlet
UserInfo, that requires creation of preference objects. Preference objects by OJB.
Looking at the code used by the ojb object broker
http://cvs.apache.org/viewcvs.cgi/db-ojb/src/java/org/apache/ojb/broker/util/ConstructorHelper.java
you see object creation taking place as:
result = constructor.newInstance(NO_ARGS);
And this code fails, class not found exception. It fails to find the object to be
created, such as a NodeImpl, which is deployed in the Prefs jar, normally stored under
jetspeed's WEB-INF/lib directory. This tells me that the classloader being used by the
code above is not the same as the cross-context classloader in Tomcat...or...the
Tomcat cross-context class loader is not designed to handle this kind of object
construction.
I think we have several directions we can take for a solution
One experiment I tried with Tomcat 4.1.30, was:
1. move every jar out of WEB-INF/lib into shared/lib
2. delete all classes under WEB-INF/classes
3. copy jetspeed-2.0-a1.jar into shared/lib
4. move the JDBC driver into Tomcat's system directory or into the classpath
This seemed to work, although for some reason I could not login to the LoginPortlet
tonight. Not sure if its related. The solution is simple here: move everything down
into shared/lib.
A second solution would be to replace Tomcat's cross-context class loader with our own.
Although I have not found a 'pluggable' way to do this.
A third solution would be to modify the classloader in the Jetspeed common servlet.
I have started some testing in this area without any success (yet):
http://cvs.apache.org/viewcvs.cgi/jakarta-jetspeed-2/commons/src/java/org/apache/jetspeed/container/JetspeedContainerServlet.java
see the infuseClasspath method
---------------------------------------------------------------------
JIRA INFORMATION:
This message is automatically generated by JIRA.
If you think it was sent incorrectly contact one of the administrators:
http://issues.apache.org/jira/secure/Administrators.jspa
If you want more information on JIRA, or have a bug to report see:
http://www.atlassian.com/software/jira
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]