persistence@glassfish.java.net

Re: A Question about code in EntityMangerSetupImpl

From: Mitesh Meswani <Mitesh.Meswani_at_Sun.COM>
Date: Tue, 29 May 2007 15:46:18 -0700
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 -----
From: Mitesh Meswani
To: persistence@glassfish.dev.java.net
Cc: Timothy.Quinn@Sun.COM
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