Repository: sqoop Updated Branches: refs/heads/sqoop2 2ee3bdd34 -> 7c7932df4
SQOOP-2577: Sqoop2: Support loading classes using custom classloader in ClassUtils (Dian Fu via Jarek Jarcec Cecho) Project: http://git-wip-us.apache.org/repos/asf/sqoop/repo Commit: http://git-wip-us.apache.org/repos/asf/sqoop/commit/7c7932df Tree: http://git-wip-us.apache.org/repos/asf/sqoop/tree/7c7932df Diff: http://git-wip-us.apache.org/repos/asf/sqoop/diff/7c7932df Branch: refs/heads/sqoop2 Commit: 7c7932df42eff29dcd1759fd8587e7a90a653df5 Parents: 2ee3bdd Author: Jarek Jarcec Cecho <[email protected]> Authored: Mon Sep 28 15:06:48 2015 -0700 Committer: Jarek Jarcec Cecho <[email protected]> Committed: Mon Sep 28 15:06:48 2015 -0700 ---------------------------------------------------------------------- .../java/org/apache/sqoop/utils/ClassUtils.java | 111 ++++++++++++++++--- .../org/apache/sqoop/utils/TestClassUtils.java | 10 ++ 2 files changed, 105 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/sqoop/blob/7c7932df/common/src/main/java/org/apache/sqoop/utils/ClassUtils.java ---------------------------------------------------------------------- diff --git a/common/src/main/java/org/apache/sqoop/utils/ClassUtils.java b/common/src/main/java/org/apache/sqoop/utils/ClassUtils.java index 6262802..3c073ca 100644 --- a/common/src/main/java/org/apache/sqoop/utils/ClassUtils.java +++ b/common/src/main/java/org/apache/sqoop/utils/ClassUtils.java @@ -17,10 +17,14 @@ */ package org.apache.sqoop.utils; +import java.lang.ref.WeakReference; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; import org.apache.sqoop.classification.InterfaceAudience; import org.apache.sqoop.classification.InterfaceStability; @@ -32,6 +36,29 @@ public final class ClassUtils { private static final Logger LOG = Logger.getLogger(ClassUtils.class); + private static final Map<ClassLoader, Map<String, WeakReference<Class<?>>>> + CACHE_CLASSES = new WeakHashMap<ClassLoader, Map<String, WeakReference<Class<?>>>>(); + + /** + * Sentinel value to store negative cache results in {@link #CACHE_CLASSES}. + */ + private static final Class<?> NEGATIVE_CACHE_SENTINEL = + NegativeCacheSentinel.class; + + private static ClassLoader defaultClassLoader; + static { + defaultClassLoader = Thread.currentThread().getContextClassLoader(); + if (defaultClassLoader == null) { + defaultClassLoader = ClassUtils.class.getClassLoader(); + } + } + + /** + * A unique class which is used as a sentinel value in the caching + * for loadClass. + */ + private static abstract class NegativeCacheSentinel {} + /** * Load class by given name and return corresponding Class object. * @@ -42,30 +69,58 @@ public final class ClassUtils { * @return Class instance or NULL */ public static Class<?> loadClass(String className) { + return loadClassWithClassLoader(className, defaultClassLoader); + } + + /** + * Load class by given name and classLoader. + * + * This method will return null in case that the class is not found, no + * exception will be rised. + * + * @param className Name of class + * @param loader classLoader to load the given class + * @return Class instance or NULL + */ + public static Class<?> loadClassWithClassLoader(String className, ClassLoader loader) { if(className == null) { return null; } + Map<String, WeakReference<Class<?>>> map; + synchronized (CACHE_CLASSES) { + map = CACHE_CLASSES.get(loader); + if (map == null) { + map = Collections.synchronizedMap( + new WeakHashMap<String, WeakReference<Class<?>>>()); + CACHE_CLASSES.put(loader, map); + } + } + Class<?> klass = null; - try { - klass = Class.forName(className); - } catch (ClassNotFoundException ex) { - LOG.debug("Exception while loading class: " + className, ex); + WeakReference<Class<?>> ref = map.get(className); + if (ref != null) { + klass = ref.get(); } if (klass == null) { - // Try the context class loader if one exists - ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader(); - if (ctxLoader != null) { - try { - klass = ctxLoader.loadClass(className); - } catch (ClassNotFoundException ex) { - LOG.debug("Exception while load class: " + className, ex); - } + try { + klass = Class.forName(className, true, loader); + } catch (ClassNotFoundException ex) { + // Leave a marker that the class isn't found + map.put(className, new WeakReference<Class<?>>(NEGATIVE_CACHE_SENTINEL)); + LOG.debug("Exception while loading class: " + className, ex); + return null; } + // two putters can race here, but they'll put the same class + map.put(className, new WeakReference<Class<?>>(klass)); + return klass; + } else if (klass == NEGATIVE_CACHE_SENTINEL) { + return null; // not found + } else { + // cache hit + return klass; } - - return klass; } /** @@ -79,7 +134,20 @@ public final class ClassUtils { * @return Instance of new class or NULL in case of any error */ public static Object instantiate(String className, Object ... args) { - return instantiate(loadClass(className), args); + return instantiateWithClassLoader(className, defaultClassLoader, args); + } + + /** + * Create instance of given class and given parameters. + * + * @param className Class name + * @param loader classLoader to load the given class + * @param args Objects that should be passed as constructor arguments + * @return Instance of new class or NULL in case of any error + */ + public static Object instantiateWithClassLoader(String className, + ClassLoader loader, Object... args) { + return instantiate(loadClassWithClassLoader(className, loader), args); } /** @@ -123,7 +191,18 @@ public final class ClassUtils { * @return Path on local filesystem to jar where given jar is present */ public static String jarForClass(String className) { - Class klass = loadClass(className); + return jarForClassWithClassLoader(className, defaultClassLoader); + } + + /** + * Return jar path for given class. + * + * @param className Class name + * @param loader classLoader to load the given class + * @return Path on local filesystem to jar where given jar is present + */ + public static String jarForClassWithClassLoader(String className, ClassLoader loader) { + Class klass = loadClassWithClassLoader(className, loader); return jarForClass(klass); } http://git-wip-us.apache.org/repos/asf/sqoop/blob/7c7932df/common/src/test/java/org/apache/sqoop/utils/TestClassUtils.java ---------------------------------------------------------------------- diff --git a/common/src/test/java/org/apache/sqoop/utils/TestClassUtils.java b/common/src/test/java/org/apache/sqoop/utils/TestClassUtils.java index 4126e7b..eca3505 100644 --- a/common/src/test/java/org/apache/sqoop/utils/TestClassUtils.java +++ b/common/src/test/java/org/apache/sqoop/utils/TestClassUtils.java @@ -17,9 +17,12 @@ */ package org.apache.sqoop.utils; +import java.util.Arrays; + import org.testng.annotations.Test; import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNull; @@ -35,6 +38,13 @@ public class TestClassUtils { } @Test + public void testLoadClassWithClassLoader() throws Exception { + String classpath = ClassUtils.jarForClass(A.class); + assertNotEquals(A.class, ClassUtils.loadClassWithClassLoader(A.class.getName(), + new ConnectorClassLoader(classpath, getClass().getClassLoader(), Arrays.asList("java.")))); + } + + @Test public void testInstantiateNull() { assertNull(ClassUtils.instantiate((Class) null)); }
