Hi Andrei,
Thanks for the explanation. Please see inline for more comments.
Andrei Ilitchev wrote:
Hi Mitesh,
The reason updateServerPlatform
method uses loader parameter is that the server platform class may be
defined in the persistence unit, and therefore the "main" classloader
used to load TopLink classes may not define this class.
However the classes defined in the
persistence unit should be able to "see" TopLink classes defined by the
"main" classloader (the most simple implementation would be the passed
class loader is a child of the "main" classloader).
Therefore I don't see how the
problem may arise when updateServerPlatform is called by deploy method
with the "real" classloader.
However there may be a problem when
the method is called earlier, by predeploy method using temporary
classloader (which is also supposed to see TopLink classes):
If toplink-e classes are loaded by a classloader higher up in hierarchy
than the classloader that loaded the persistence unit, there is no
issue. But, if toplink-e classes are loaded by the same classloader
that loaded the persistence unit, the code breaks. This is the case
when toplink is used from within appclient of glassfish. To give you
some more details - The classloader hierarchy for the appclient is as
follows
SystemClassLoader (Has JDK and couple of appserver jars in
classpath)
|
|
ApplicationClassLoader (an instance of EJBClassLoader and has the
client.jar, derby.jar and
toplink-e.jar
in classpath)
Please note that it is expected that persistence provider jars and jdbc
driver jars can be provided by the user. Hence it is the
ApplicationClassLoader that loads toplink-e.jar.
Now, during predeploy, EntityMangerSetupImpl#updateServerPlatform(Map
m, ClassLoader loader) is called with loader set to the
tempClassLoader, which is a clone of ApplicationClassLoader. When
following code is executed with this loader,
Class cls = findClassForProperty(serverPlatformClassName,
TopLinkProperties.TARGET_SERVER, loader);
cls is loaded by the tempClassLoader and is unusable because we try to
find a constructor for this cls that takes
'oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.class'
loaded by the original ApplicationClassLoader as a parameter.
Is it acceptable to use ContextClassLoader at this point? I quickly
hacked appclient container code to set correct context class loader and
it seems to solve the issue. Following is the change that I am
proposing.
$ cvs -q diff -u
entity-persistence/src/java/oracle/toplink/essentials/internal/ejb/cmp3/EntityManagerSetupImpl.java
diff -u -r1.54 EntityManagerSetupImpl.java
---
entity-persistence/src/java/oracle/toplink/essentials/internal/ejb/cmp3/EntityManagerSetupImpl.java
24 May 2007 17:25:48 -0000 1.54
+++
entity-persistence/src/java/oracle/toplink/essentials/internal/ejb/cmp3/EntityManagerSetupImpl.java
29 May 2007 22:14:36 -0000
@@ -319,7 +319,7 @@
// the new serverPlatform
ServerPlatform serverPlatform = null;
// New platform - create the new instance and set it.
- Class cls = findClassForProperty(serverPlatformClassName,
TopLinkProperties.TARGET_SERVER, loader);
+ Class cls = findClassForProperty(serverPlatformClassName,
TopLinkProperties.TARGET_SERVER,
Thread.currentThread().getContextClassLoader());
try {
Constructor constructor = cls.getConstructor(new
Class[]{oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.class});
serverPlatform =
(ServerPlatform)constructor.newInstance(new Object[]{session});
Thanks,
Mitesh
the server platform instantiated
using temporary classloader will be set - and never overridden with the
same one loaded using the "real" classloader. Again, that only
applicable in case of user-defined server platform. The same applies also to updateLoggers.
The only reason to have
updateServerPlatform and updateLoggers in predeploy is to make sure
that the correct logger is used in predeploy method.
updateServerPlatform and
updateLoggers methods called for the second time in deploy method
(through updateServerSession method): at this point if server platform
class name is the same as the original one then the new server platform
is not created (the same for loggers).
What probably should happen: in case
serverPlatform's class name is the same as the specified, verify that
it's classloader is the "main" one (the same one used to load TopLink
classes). Otherwise it's a user-defined platform loaded on the
temporary classloader - then it should be overridden with the new one
(the same class name, but using the passed ("real") classloader).
Please let me know does that make
sense to you.
Thanks,
Andrei
-----
Original Message -----
Sent:
Thursday, May 24, 2007 9:16 PM
Subject:
A Question about code in EntityMangerSetupImpl
Hi Tom, Gordon,
I have a question about following code from EntityMangerSetupImpl
protected boolean updateServerPlatform(Map m, ClassLoader loader) {
String serverPlatformClassName =
PropertiesHandler.getPropertyValueLogDebug(TopLinkProperties.TARGET_SERVER,
m, session);
....
....
ServerPlatform serverPlatform = null;
// New platform - create the new instance and set it.
--->A Class cls =
findClassForProperty(serverPlatformClassName,
TopLinkProperties.TARGET_SERVER, loader);
try {
Class clz =
oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.class;
--->B Constructor constructor =
cls.getConstructor(new
Class[]{oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.class});
serverPlatform =
(ServerPlatform)constructor.newInstance(new Object[]{session});
Here depending on from which environment and from which method
updateServerPlatform is called, the parameter loader is either the
classloader that loads the application (PereistenceUnit) or the
tempClassLoader which is expected to be a clone.
At point (A) above, we use the parameter 'loader' as the classloader to
load the class specified by property TopLinkProperties.TARGET_SERVER.
At point (B) we try to get constructor for cls with parameter
'oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl.class'.
If 'cls' is loaded by a different classloader than the classloader that
loads 'DatabaseSessionImpl.class', the constructor will never be found.
Infect, this is what is happening inside appclient for glassfish and we
are not able to inject persistence artifacts into appclient.
The question is at point (A) above, why do we use the parameter
'loader' as the classloader to load the class specified by property
TopLinkProperties.TARGET_SERVER?
Thanks,
Mitesh