users@glassfish.java.net

Re: GF 3.1.1 Classloading issue ... how to tackle it?

From: Andreas Loew <Andreas.Loew_at_oracle.com>
Date: Mon, 28 Nov 2011 22:48:54 +0100

Hi Bernhard,

hmm - if the name of your property file is the same X, but only the
content is different for both classloading scenarios, then - as you have
already found out - the solution a just proposed in the previous reply
by Bryan / User batsatt will not work:

As the resource file X will already have been loaded from the common
classloader, the EJB classloader will delegate any further requests to
load resource X to its parent loaders and not load the different
incarnation of X from its own EJB-JAR.

So it indeed looks like the only way to overcome this issue is to set up
a custom non-delegating classloader within the EJB container, which is
quite a demanding, but interesting programming task... :-)

Firstly, I'd like to point you to One-JAR:

http://one-jar.sourceforge.net

where basically nested JARs are implemented in a similar way by defining
an application-specific classloader that does a very custom job.

I have used the One-JAR library at some point in the past and
modified/extended its com.simontuffs.onejar.JarClassLoader in a way that
it becomes a non-delegating classloader - which is what you will need
from the EJB container, though not for nested JAR files. But you will
get the basic idea of how a non-delegating classloader should look like:

package com.blabla.util;

import com.simontuffs.onejar.JarClassLoader;

/**
  * NonDelegatingClassLoader is a custom, non-delegating classloader for the
  * Blabla framework.
  * <p>
  * It freshly loads all Blabla-related classes from this classloader,
thereby
  * overriding original Hubba classes that come with Foobar App Server and
  * therefore enabling Blaba to be used from a Foobar Doodle.
  * <p>
  * Classes not explicitly included in the non-delegation list as
contained in
  * method {_at_link loadClass()} will be loaded from the parent classloader as
  * usual.
  */
public class NonDelegatingClassLoader extends JarClassLoader {

     /**
      * NonDelegatingClassLoader debug system property name: If set to
true, will
      * write debug output to System.err.
      */
     public static final String DEBUG_PROPERTY =
"com.blaba.util.NonDelegatingClassLoader.debug";

     /**
      * Debug flag. Default is <code>false</code>.
      */
     protected boolean debug = false;

     /**
      * Nested exception class thrown and immediately caught to create stack
      * traces on demand.
      */
     public static class ForDebuggingOnlyNoRealException extends Exception {
     private static final long serialVersionUID = 1L;
     }

     /**
      * Construct a new NonDelegatingClassLoader instance.
      *
      * @param parent the parent classloader
      */
     public NonDelegatingClassLoader(ClassLoader parent) {
     super(parent);
     debug = false;
     if (System.getProperty(DEBUG_PROPERTY,
"false").equalsIgnoreCase("true")) {
         debug = true;
     }
     if (debug) {
         System.err.println("Parent classloader of
NonDelegatingClassLoader is " + parent);
     }
     }

     /**
      * {_at_inheritDoc}
      */
     protected synchronized Class<?> loadClass(String name, boolean
resolve) throws ClassNotFoundException {

     // is the class already loaded/available from this classloader?
     Class<?> c = findLoadedClass(name);

     if (c == null) {

         // is the class name to be loaded included in the
non-delegation list?
         if (!(name.startsWith("com.hubba."))) {

         // no, it is not - therefore delegate to the parent classloader
         if (debug) {
             try {
             throw new ForDebuggingOnlyNoRealException();
             } catch (ForDebuggingOnlyNoRealException e) {
             System.err.println("Loading class " + name + " from parent
classloader");
             e.printStackTrace();
             }
         }
         try {
             c = getParent().loadClass(name);
         } catch (ClassNotFoundException e) {
             if (debug) {
             System.err.println("Class " + name + " not found in parent
classloader");
             }
         }
         }

         if (c == null) {

         // load class from this NonDelegatingClassLoader
         try {
             if (debug) {
             try {
                 throw new ForDebuggingOnlyNoRealException();
             } catch (ForDebuggingOnlyNoRealException e) {
                 System.err.println("Loading class " + name + " from
NonDelegatingClassLoader");
                 e.printStackTrace();
             }
             }
             c = findClass(name);

         } catch (ClassNotFoundException cnfe) {
             if (debug) {
             System.err.println("Class " + name + " not found in
NonDelegatingClassLoader");
             }
         }

         // if not available from this NonDelegatingClassLoader, only
then try its parent
         if (c == null) {
             if (debug) {
             try {
                 throw new ForDebuggingOnlyNoRealException();
             } catch (ForDebuggingOnlyNoRealException cnfe) {
                 System.err.println("Loading class " + name + " from
parent classloader (2)");
                 cnfe.printStackTrace();
             }
             }
             try {
             c = getParent().loadClass(name);
             } catch (ClassNotFoundException cnfe) {
             if (debug) {
                 System.err.println("Class " + name + " not found in
parent classloader (2)");
                 cnfe.printStackTrace();
             }
             throw cnfe;
             }

         }

         }
     }

     if (resolve) {
         resolveClass(c);
     }

     return c;

     }

}


Any use of the custom classloader needs to be coded similar to the
following then:

     // determine and store away current Java EE context classloader
     ClassLoader oldcl = Thread.currentThread().getContextClassLoader();
     LOG.debug("saved old context class loader " + oldcl);
     try {
         // load the true ("delegate") implementation class from the
custom non-delegating classloader
         Class<?> cls =
NON_DELEGATING_CLASSLOADER.loadClass(DELEGATE_CLASS_NAME);
         // and make the custom classloader the current Java EE context
classloader
         
Thread.currentThread().setContextClassLoader(NON_DELEGATING_CLASSLOADER);

>>> here you need to code access to your properties file instead by
using NON_DELEGATING_CLASSLOADER./getResourceAsStream/(name)

         // create the delegate instance
         Blabla myBlablaImpl = (Blabla) cls.newInstance();
         // and forward the inbound call to the delegate object
         return myBlablaImpl.blabla();

<<< here you need to code access to your properties file instead by
using NON_DELEGATING_CLASSLOADER./getResourceAsStream/(name)

     } catch (Exception ex) {
         (...)
     } finally {
         // restore the previous context class loader
         Thread.currentThread().setContextClassLoader(oldcl);
         LOG.debug("restored old context class loader " + oldcl);
     }


You can also look here:

http://www.xinotes.org/notes/note/444/

for another sample of a non-delegating clasloader for a specific scenario...

Hope this helps & best regards,

Andreas


P.S.:
If you have any further questions, don't hesitate to contact me directly ;-)
I am unfortunately still on sick leave, but will hopefully be back at
work early next year...

-- 
Andreas Loew | Senior Java Architect
Oracle Advanced Customer Services
ORACLE Deutschland B.V. & Co. KG