Merge pull request #123 from apache/bugfix/UIMA-6275-InitializableFactory-is-not-smart-enough-to-find-a-suitable-classloader

[UIMA-6275] InitializableFactory is not smart enough to find a suitable classloader
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/factory/FsIndexFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/factory/FsIndexFactory.java
index bfc7923..a210ef7 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/factory/FsIndexFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/factory/FsIndexFactory.java
@@ -31,6 +31,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.uima.fit.descriptor.FsIndex;
 import org.apache.uima.fit.descriptor.FsIndexKey;
+import org.apache.uima.fit.internal.ClassloaderUtils;
 import org.apache.uima.fit.internal.MetaDataType;
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.metadata.FsIndexCollection;
@@ -43,7 +44,6 @@
 import org.apache.uima.resource.metadata.impl.Import_impl;
 import org.apache.uima.util.InvalidXMLException;
 import org.apache.uima.util.XMLInputSource;
-import org.springframework.util.ClassUtils;
 
 /**
  */
@@ -259,7 +259,7 @@
    *           if the index collection could not be assembled
    */
   public static FsIndexCollection createFsIndexCollection() throws ResourceInitializationException {
-    ClassLoader cl = ClassUtils.getDefaultClassLoader();
+    ClassLoader cl = ClassloaderUtils.findClassloader();
     FsIndexCollection aggFsIdxCol = fsIndexCollectionsByClassloader.get(cl);
     if (aggFsIdxCol == null) {
       synchronized (CREATE_LOCK) {
@@ -275,7 +275,7 @@
             throw new ResourceInitializationException(e);
           } catch (InvalidXMLException e) {
             LogFactory.getLog(FsIndexFactory.class)
-                    .warn("[" + location + "] is not a index descriptor file. Ignoring.", e);
+            .warn("[" + location + "] is not a index descriptor file. Ignoring.", e);
           }
         }
 
@@ -299,7 +299,7 @@
    */
   public static String[] scanIndexDescriptors() throws ResourceInitializationException {
     synchronized (SCAN_LOCK) {
-      ClassLoader cl = ClassUtils.getDefaultClassLoader();
+      ClassLoader cl = ClassloaderUtils.findClassloader();
       String[] indexLocations = fsIndexLocationsByClassloader.get(cl);
       if (indexLocations == null) {
         indexLocations = scanDescriptors(MetaDataType.FS_INDEX);
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypePrioritiesFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypePrioritiesFactory.java
index 1759bc9..c79abaa 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypePrioritiesFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypePrioritiesFactory.java
@@ -28,6 +28,7 @@
 import java.util.WeakHashMap;
 
 import org.apache.commons.logging.LogFactory;
+import org.apache.uima.fit.internal.ClassloaderUtils;
 import org.apache.uima.fit.internal.MetaDataType;
 import org.apache.uima.fit.internal.ResourceManagerFactory;
 import org.apache.uima.resource.ResourceInitializationException;
@@ -38,7 +39,6 @@
 import org.apache.uima.util.CasCreationUtils;
 import org.apache.uima.util.InvalidXMLException;
 import org.apache.uima.util.XMLInputSource;
-import org.springframework.util.ClassUtils;
 
 public final class TypePrioritiesFactory {
   private static final Object SCAN_LOCK = new Object();
@@ -105,7 +105,7 @@
    *           if the collected type priorities cannot be merged.
    */
   public static TypePriorities createTypePriorities() throws ResourceInitializationException {
-    ClassLoader cl = ClassUtils.getDefaultClassLoader();
+    ClassLoader cl = ClassloaderUtils.findClassloader();
     TypePriorities aggTypePriorities = typePrioritiesByClassloader.get(cl);
     if (aggTypePriorities == null) {
       synchronized (CREATE_LOCK) {
@@ -117,7 +117,7 @@
             typePriorities.resolveImports();
             typePrioritiesList.add(typePriorities);
             LogFactory.getLog(TypePrioritiesFactory.class)
-                    .debug("Detected type priorities at [" + location + "]");
+            .debug("Detected type priorities at [" + location + "]");
           } catch (IOException e) {
             throw new ResourceInitializationException(e);
           } catch (InvalidXMLException e) {
@@ -146,7 +146,7 @@
    */
   public static String[] scanTypePrioritiesDescriptors() throws ResourceInitializationException {
     synchronized (SCAN_LOCK) {
-      ClassLoader cl = ClassUtils.getDefaultClassLoader();
+      ClassLoader cl = ClassloaderUtils.findClassloader();
       String[] typePrioritesLocations = typePrioritesLocationsByClassloader.get(cl);
       if (typePrioritesLocations == null) {
         typePrioritesLocations = scanDescriptors(MetaDataType.TYPE_PRIORITIES);
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypeSystemDescriptionFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypeSystemDescriptionFactory.java
index ab3e9f8..dd2a246 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypeSystemDescriptionFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/factory/TypeSystemDescriptionFactory.java
@@ -28,6 +28,7 @@
 import java.util.WeakHashMap;
 
 import org.apache.commons.logging.LogFactory;
+import org.apache.uima.fit.internal.ClassloaderUtils;
 import org.apache.uima.fit.internal.MetaDataType;
 import org.apache.uima.fit.internal.ResourceManagerFactory;
 import org.apache.uima.resource.ResourceInitializationException;
@@ -38,7 +39,6 @@
 import org.apache.uima.resource.metadata.impl.TypeSystemDescription_impl;
 import org.apache.uima.util.InvalidXMLException;
 import org.apache.uima.util.XMLInputSource;
-import org.springframework.util.ClassUtils;
 
 public final class TypeSystemDescriptionFactory {
   private static final Object SCAN_LOCK = new Object();
@@ -110,7 +110,7 @@
    */
   public static TypeSystemDescription createTypeSystemDescription()
           throws ResourceInitializationException {
-    ClassLoader cl = ClassUtils.getDefaultClassLoader();
+    ClassLoader cl = ClassloaderUtils.findClassloader();
     TypeSystemDescription tsd = typeDescriptorByClassloader.get(cl);
     if (tsd == null) {
       synchronized (CREATE_LOCK) {
@@ -120,12 +120,12 @@
             XMLInputSource xmlInputType1 = new XMLInputSource(location);
             tsdList.add(getXMLParser().parseTypeSystemDescription(xmlInputType1));
             LogFactory.getLog(TypeSystemDescription.class)
-                .debug("Detected type system at [" + location + "]");
+            .debug("Detected type system at [" + location + "]");
           } catch (IOException e) {
             throw new ResourceInitializationException(e);
           } catch (InvalidXMLException e) {
             LogFactory.getLog(TypeSystemDescription.class)
-                .warn("[" + location + "] is not a type file. Ignoring.", e);
+            .warn("[" + location + "] is not a type file. Ignoring.", e);
           }
         }
 
@@ -148,7 +148,7 @@
    */
   public static String[] scanTypeDescriptors() throws ResourceInitializationException {
     synchronized (SCAN_LOCK) {
-      ClassLoader cl = ClassUtils.getDefaultClassLoader();
+      ClassLoader cl = ClassloaderUtils.findClassloader();
       String[] typeDescriptorLocations = typeDescriptorLocationsByClassloader.get(cl);
       if (typeDescriptorLocations == null) {
         typeDescriptorLocations = scanDescriptors(MetaDataType.TYPE_SYSTEM);
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/factory/initializable/InitializableFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/factory/initializable/InitializableFactory.java
index e0c54e6..906538a 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/factory/initializable/InitializableFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/factory/initializable/InitializableFactory.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -19,11 +19,12 @@
 package org.apache.uima.fit.factory.initializable;
 
 import org.apache.uima.UimaContext;
+import org.apache.uima.fit.internal.ClassloaderUtils;
 import org.apache.uima.resource.ResourceInitializationException;
 
 /**
  * Please see {@link Initializable} for a description of how this class is intended to be used.
- * 
+ *
  * @see Initializable
  */
 public final class InitializableFactory {
@@ -35,7 +36,7 @@
    * Provides a way to create an instance of T. If the class specified by className implements
    * {@link Initializable}, then the UimaContext provided here will be passed to its initialize
    * method.
-   * 
+   *
    * @param <T>
    *          the interface type
    * @param context
@@ -50,7 +51,14 @@
    */
   public static <T> T create(UimaContext context, String className, Class<T> superClass)
           throws ResourceInitializationException {
-    Class<? extends T> cls = getClass(className, superClass);
+    Class<? extends T> cls;
+    try {
+      ClassLoader cl = ClassloaderUtils.findClassloader(context);
+      cls = Class.forName(className, true, cl).asSubclass(superClass);
+    } catch (Exception e) {
+      throw new ResourceInitializationException(new IllegalStateException("classname = "
+              + className + " superClass = " + superClass.getName(), e));
+    }
     return create(context, cls);
   }
 
@@ -68,7 +76,8 @@
   public static <T> Class<? extends T> getClass(String className, Class<T> superClass)
           throws ResourceInitializationException {
     try {
-      return Class.forName(className).asSubclass(superClass);
+      ClassLoader cl = ClassloaderUtils.findClassloader();
+      return Class.forName(className, true, cl).asSubclass(superClass);
     } catch (Exception e) {
       throw new ResourceInitializationException(new IllegalStateException("classname = "
               + className + " superClass = " + superClass.getName(), e));
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/internal/ClassloaderUtils.java b/uimafit-core/src/main/java/org/apache/uima/fit/internal/ClassloaderUtils.java
new file mode 100644
index 0000000..2dc3671
--- /dev/null
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/internal/ClassloaderUtils.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.uima.fit.internal;
+
+import org.apache.uima.UimaContext;
+import org.apache.uima.UimaContextAdmin;
+import org.apache.uima.UimaContextHolder;
+import org.apache.uima.resource.ResourceManager;
+import org.springframework.util.ClassUtils;
+
+/**
+ * INTERNAL API - Helper functions to obtain a suitable classloader.
+ */
+public final class ClassloaderUtils {
+  private ClassloaderUtils() {
+    // No instances
+  }
+
+  /**
+   * Looks up a suitable classloader in the following order:
+   * <ol>
+   * <li>The {@link UimaContext} in the {@link UimaContextHolder} of the current thread(if any)</li>
+   * <li>The current thread-context classloader (if any)</li>
+   * <li>The classloader through which uimaFIT (i.e. this class) was loaded.</li>
+   * <li>For backwards compatibility then delegates to {@link ClassUtils#getDefaultClassLoader()}</li>
+   * </ol>
+   *
+   * @return a classloader or {@code null} if no suitable classloader could be found.
+   */
+  public static ClassLoader findClassloader() {
+    ClassLoader uimaThreadContextClassLoader = getExtensionClassloader(
+            UimaContextHolder.getContext());
+    if (uimaThreadContextClassLoader != null) {
+      return uimaThreadContextClassLoader;
+    }
+
+    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+    if (contextClassLoader != null) {
+      return contextClassLoader;
+    }
+
+    ClassLoader uimaFITClassLoader = ClassloaderUtils.class.getClassLoader();
+    if (uimaFITClassLoader != null) {
+      return uimaFITClassLoader;
+    }
+
+    return ClassUtils.getDefaultClassLoader();
+  }
+
+  /**
+   * Looks up a suitable classloader in the following order:
+   * <ol>
+   * <li>The extension classloader of the given {@link ResourceManager}</li>
+   * <li>{@see #findClassloader()}</li>
+   * </ol>
+   *
+   * @return a classloader or {@code null} if no suitable classloader could be found.
+   */
+  public static ClassLoader findClassloader(ResourceManager aResMgr) {
+    ClassLoader resourceManagerExtensionClassloader = getExtensionClassloader(aResMgr);
+    if (resourceManagerExtensionClassloader != null) {
+      return resourceManagerExtensionClassloader;
+    }
+
+    return findClassloader();
+  }
+
+  /**
+   * Looks up a suitable classloader in the following order:
+   * <ol>
+   * <li>The extension classloader of the {@link ResourceManager} associated with the given
+   * {@link UimaContext} (if any)</li>
+   * <li>{@see #findClassloader(ResourceManager)}</li>
+   * </ol>
+   *
+   * @return a classloader or {@code null} if no suitable classloader could be found.
+   */
+  public static ClassLoader findClassloader(UimaContext aContext) {
+    ClassLoader uimaContextExtensionClassloader = getExtensionClassloader(aContext);
+    if (uimaContextExtensionClassloader != null) {
+      return uimaContextExtensionClassloader;
+    }
+
+    return findClassloader((ResourceManager) null);
+  }
+
+  private static ClassLoader getExtensionClassloader(UimaContext aContext) {
+    if (aContext instanceof UimaContextAdmin) {
+      return getExtensionClassloader(((UimaContextAdmin) aContext).getResourceManager());
+    }
+
+    return null;
+  }
+
+  private static ClassLoader getExtensionClassloader(ResourceManager aResMgr) {
+    if (aResMgr == null) {
+      return null;
+    }
+
+    ClassLoader cl = aResMgr.getExtensionClassLoader();
+    if (cl != null) {
+      return aResMgr.getExtensionClassLoader();
+    }
+
+    return null;
+  }
+}
diff --git a/uimafit-core/src/main/java/org/apache/uima/fit/internal/ResourceManagerFactory.java b/uimafit-core/src/main/java/org/apache/uima/fit/internal/ResourceManagerFactory.java
index 5bf34fa..e3e40b1 100644
--- a/uimafit-core/src/main/java/org/apache/uima/fit/internal/ResourceManagerFactory.java
+++ b/uimafit-core/src/main/java/org/apache/uima/fit/internal/ResourceManagerFactory.java
@@ -6,9 +6,9 @@
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
- * 
+ *
  *   http://www.apache.org/licenses/LICENSE-2.0
- * 
+ *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@@ -28,16 +28,15 @@
 import org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.ResourceManager;
 import org.apache.uima.resource.impl.ResourceManager_impl;
-import org.springframework.util.ClassUtils;
 
 /**
  * INTERNAL API - Helper functions for dealing with resource managers and classloading
- * 
- * This API is experimental and is very likely to be removed or changed in future versions. 
+ *
+ * This API is experimental and is very likely to be removed or changed in future versions.
  */
 public class ResourceManagerFactory {
   private static ResourceManagerCreator resourceManagerCreator = new DefaultResourceManagerCreator();
-  
+
   private ResourceManagerFactory() {
     // No instances
   }
@@ -46,7 +45,7 @@
   {
     return resourceManagerCreator.newResourceManager();
   }
-  
+
   /**
    * Mind that returning a singleton resource manager from {@link ResourceManagerFactory} is
    * generally a bad idea because it gets destroyed on a regular basis. For this reason, it is
@@ -57,15 +56,15 @@
           ResourceManagerCreator resourceManagerCreator) {
     ResourceManagerFactory.resourceManagerCreator = resourceManagerCreator;
   }
-  
+
   public static ResourceManagerCreator getResourceManagerCreator() {
     return resourceManagerCreator;
   }
-  
-  public static interface ResourceManagerCreator {
+
+  public interface ResourceManagerCreator {
     ResourceManager newResourceManager() throws ResourceInitializationException;
   }
-  
+
   public static class DefaultResourceManagerCreator implements ResourceManagerCreator {
     @Override
     public ResourceManager newResourceManager() throws ResourceInitializationException {
@@ -78,37 +77,35 @@
           // See https://issues.apache.org/jira/browse/UIMA-5056
           return ((UimaContextAdmin) activeContext).getResourceManager();
         }
-        else {
-          // If there is no UIMA context, then we create a new resource manager
-          // UIMA core still does not fall back to the context classloader in all cases.
-          // This was the default behavior until uimaFIT 2.2.0.
-          ResourceManager resMgr;
-          if (Thread.currentThread().getContextClassLoader() != null) {
-            // If the context classloader is set, then we want the resource manager to fallb
-            // back to it. However, it may not reliably do that that unless we explictly pass
-            // null here. See. UIMA-6239.
-            resMgr = new ResourceManager_impl(null);
-          }
-          else {
-            resMgr = UIMAFramework.newDefaultResourceManager();
-          }
-          
-          
-          // Since UIMA Core version 2.10.3 and 3.0.1 the thread context classloader is taken
-          // into account by the core framework. Thus, we no longer have to explicitly set a
-          // classloader these or more recent versions. (cf. UIMA-5802)
-          short maj = UimaVersion.getMajorVersion();
-          short min = UimaVersion.getMinorVersion();
-          short rev = UimaVersion.getBuildRevision();
-          boolean uimaCoreIgnoresContextClassloader = 
-                  (maj == 2 && (min < 10 || (min == 10 && rev < 3))) || // version < 2.10.3
-                  (maj == 3 && ((min == 0 && rev < 1)));                // version < 3.0.1
-          if (uimaCoreIgnoresContextClassloader) {
-            resMgr.setExtensionClassPath(ClassUtils.getDefaultClassLoader(), "", true);
-          }
-          
-          return resMgr;
+
+        // If there is no UIMA context, then we create a new resource manager
+        // UIMA core still does not fall back to the context classloader in all cases.
+        // This was the default behavior until uimaFIT 2.2.0.
+        ResourceManager resMgr;
+        if (Thread.currentThread().getContextClassLoader() != null) {
+          // If the context classloader is set, then we want the resource manager to fallb
+          // back to it. However, it may not reliably do that that unless we explictly pass
+          // null here. See. UIMA-6239.
+          resMgr = new ResourceManager_impl(null);
         }
+        else {
+          resMgr = UIMAFramework.newDefaultResourceManager();
+        }
+
+        // Since UIMA Core version 2.10.3 and 3.0.1 the thread context classloader is taken
+        // into account by the core framework. Thus, we no longer have to explicitly set a
+        // classloader these or more recent versions. (cf. UIMA-5802)
+        short maj = UimaVersion.getMajorVersion();
+        short min = UimaVersion.getMinorVersion();
+        short rev = UimaVersion.getBuildRevision();
+        boolean uimaCoreIgnoresContextClassloader =
+                (maj == 2 && (min < 10 || (min == 10 && rev < 3))) || // version < 2.10.3
+                (maj == 3 && ((min == 0 && rev < 1)));                // version < 3.0.1
+        if (uimaCoreIgnoresContextClassloader) {
+          resMgr.setExtensionClassPath(ClassloaderUtils.findClassloader(), "", true);
+        }
+
+        return resMgr;
       }
       catch (MalformedURLException e) {
         throw new ResourceInitializationException(e);