Dev/classLoading/threadContext

From CubeiaWiki

Jump to: navigation, search
 Note: From Firebase version 1.9 the thread context class loaders should be set correctly everywhere and the information on this page is deprecated.

Libraries and Context Class Loading

Libraries that depends on the thread context class loader may cause problems in Firebase. And this may cause confusion in the class loading. An example is Apache Commons Logging which, if used in a service, results in an exception looking very much like this:

*** Failed to start server:
[...]
Caused by: org.apache.commons.logging.LogConfigurationException: org.apache.commons.logging.LogConfigurationException: I
nvalid class loader hierarchy.  You have more than one version of 'org.apache.commons.logging.Log' visible, which is not
allowed. (Caused by org.apache.commons.logging.LogConfigurationException: Invalid class loader hierarchy.  You have mor
e than one version of 'org.apache.commons.logging.Log' visible, which is not allowed.)
      at org.apache.commons.logging.impl.LogFactoryImpl.getLogConstructor(LogFactoryImpl.java:397)
      at org.apache.commons.logging.impl.LogFactoryImpl.newInstance(LogFactoryImpl.java:529)
      ... 20 more
Caused by: org.apache.commons.logging.LogConfigurationException: Invalid class loader hierarchy.  You have more than one
version of 'org.apache.commons.logging.Log' visible, which is not allowed.
      at org.apache.commons.logging.impl.LogFactoryImpl.getLogConstructor(LogFactoryImpl.java:385)
      ... 21 more

What have happened above is that the context class loader is set to something internal to Firebase, and does contain a "Log" class and this conflicts with your implementation. So far this is not handled transparently in Firebase as it would mean proxying every service call, however if it is a major problem for you, please contact us with a feature request.

Resolution

There are two issues at hand:

  1. You may have to set the context class loader manually.
  2. You must make sure the context class loader is reset when you're finished.

So, given method call "utilizeContextClass" which causes the conflict, you can do this:

public void testSwitch() {
    Thread th = Thread.currentThread();
    ClassLoader loader = getClass().getClassLoader();
    ClassLoader old = th.getContextClassLoader();
    try {
        th.setContextClassLoader(loader);
        utilizeContextClass(); // This would be your own logic
    } finally {
        th.setContextClassLoader(old);
    }
} 

In the above you set and reset the class loader on the current thread before the offending invokation. Obviously this is somewhat cumbersome, for a generic solution, see below.

A Generic Utility

Internally in Firebase we have created two utility classes to deal with calls like this, first an invocation interface:

public interface InvocationFacade<S> {

    public S invoke();
	
}

Then a static utility method for using the above facade:

public class ClassUtil {

    private ClassUtil() { }
	
    public static <S> S switchContextClassLoaderForInvocation(InvocationFacade<S> facade, ClassLoader loader) {
        Thread th = Thread.currentThread();
        ClassLoader old = th.getContextClassLoader();        
        try {
            th.setContextClassLoader(loader);
            return facade.invoke();
        } finally {
            th.setContextClassLoader(old);
        }
    }
}

Thus, you can use the above utility for each instance you need to invoke an offending code. So, the above example would look like this (given that "utilizeContextClass" returns a String):

public void testSwitch() {
    ClassLoader loader = getClass().getClassLoader();
    ClassUtil.switchContextClassLoaderForInvocation(new InvocationFacade<String>() {

        public String invoke() {
            return utilizeContextClass();
        }
    }, loader);  
} 
Personal tools