Bug 423933 -  J2EE Server Side Equinox very slow due to complex and
repeated class loading (trees)

https://bugs.eclipse.org/bugs/show_bug.cgi?id=423933

Backport for 3.10 branch
diff --git a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleListClassLoader.java b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleListClassLoader.java
index 327499a..899e116 100644
--- a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleListClassLoader.java
+++ b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleListClassLoader.java
@@ -1,24 +1,36 @@
 package org.eclipse.scout.commons.osgi;
 
-import java.io.IOException;
 import java.io.InputStream;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Array;
 import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Comparator;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.Vector;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.regex.Pattern;
 
 import org.eclipse.scout.commons.StringUtility;
+import org.eclipse.scout.commons.internal.Activator;
+import org.eclipse.scout.commons.serialization.SerializationUtility;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
 
 /**
  * Class loader implementation that uses a list of bundles to load classes.
- * 
+ *
  * @since 3.8.2
  */
 public class BundleListClassLoader extends ClassLoader {
@@ -47,24 +59,70 @@
     PRIMITIVE_TYPES.put("D", double.class);
     PRIMITIVE_TYPES.put("V", void.class);
   }
+  private static final ThreadLocal<Set<String>> LOOP_DETECTOR = new ThreadLocal<Set<String>>();
+  private static final Enumeration<URL> EMPTY_URL_ENUMERATION = new Enumeration<URL>() {
+    @Override
+    public boolean hasMoreElements() {
+      return false;
+    }
+
+    @Override
+    public URL nextElement() {
+      throw new NoSuchElementException();
+    }
+  };
+  private static final String BUNDLE_INCLUDE_FILTER_PROPERTY = "org.eclipse.scout.commons.osgi.BundleListClassLoader#includeBundles";
+  private static final String BUNDLE_EXCLUDE_FILTER_PROPERTY = "org.eclipse.scout.commons.osgi.BundleListClassLoader#excludeBundles";
+  private static final String REGEX_MARKER = "regex:";
+
+  private static ClassLoader s_myClassLoader;
+  static {
+    AccessController.doPrivileged(new PrivilegedAction<Object>() {
+      @Override
+      public Object run() {
+        s_myClassLoader = BundleListClassLoader.class.getClassLoader();
+        return null;
+      }
+    });
+  }
 
   private final Bundle[] m_bundles;
   private final Bundle[] m_bundlesSortedByBundleSymbolicNameLenght;
-  private final Map<String, Class<?>> m_classCache;
+  private String[] m_bundleOrderPrefixes = null;
+  private final ClassLoader m_parentContextClassLoader;
+  private final ReadWriteLock m_cacheLock = new ReentrantReadWriteLock();
+  private final Map<String, WeakReference<Class<?>>> m_classCache;
+  private final boolean m_useResourceFiltering;
+  private final boolean m_useResourceCaching;
+  private final Map<String, Vector<URL>> m_resourceCache;
 
-  public BundleListClassLoader(Bundle... bundles) {
+  public BundleListClassLoader(ClassLoader parent, Bundle... bundles) {
+    super(parent);
+    m_parentContextClassLoader = parent != null ? parent : new ClassLoader(Object.class.getClassLoader()) {
+      // boot classloader
+    };
     if (bundles == null || bundles.length == 0) {
       throw new IllegalArgumentException("bundle list must not be null or empty");
     }
     // filter given list of bundles.
-    m_bundles = BundleInspector.filterPluginBundles(bundles);
-    if (m_bundles.length == 0) {
+    String bundleIncludeFilter = Activator.getDefault().getBundle().getBundleContext().getProperty(BUNDLE_INCLUDE_FILTER_PROPERTY);
+    String bundleExcludeFilter = Activator.getDefault().getBundle().getBundleContext().getProperty(BUNDLE_EXCLUDE_FILTER_PROPERTY);
+    Pattern[] bundleIncludePatterns = parseFilterPatterns(bundleIncludeFilter);
+    Pattern[] bundleExcludePatterns = parseFilterPatterns(bundleExcludeFilter);
+    List<Bundle> filteredBundleList = new ArrayList<Bundle>();
+    for (Bundle b : BundleInspector.filterPluginBundles(bundles)) {
+      if (accept(b.getSymbolicName(), bundleIncludePatterns, bundleExcludePatterns)) {
+        filteredBundleList.add(b);
+      }
+    }
+    m_bundles = filteredBundleList.toArray(new Bundle[filteredBundleList.size()]);
+    if (getBundles().length == 0) {
       throw new IllegalArgumentException("filtered bundle list must not be empty. [bundles=" + Arrays.toString(bundles) + "]");
     }
     //
-    m_bundlesSortedByBundleSymbolicNameLenght = new Bundle[m_bundles.length];
-    System.arraycopy(m_bundles, 0, m_bundlesSortedByBundleSymbolicNameLenght, 0, m_bundles.length);
-    Arrays.sort(m_bundlesSortedByBundleSymbolicNameLenght, new Comparator<Bundle>() {
+    m_bundlesSortedByBundleSymbolicNameLenght = new Bundle[getBundles().length];
+    System.arraycopy(getBundles(), 0, getBundlesSortedByBundleSymbolicNameLenght(), 0, getBundles().length);
+    Arrays.sort(getBundlesSortedByBundleSymbolicNameLenght(), new Comparator<Bundle>() {
       @Override
       public int compare(Bundle b1, Bundle b2) {
         if (b1 == null && b2 == null) {
@@ -80,32 +138,102 @@
       }
     });
     //
-    m_classCache = new HashMap<String, Class<?>>();
+    m_classCache = new HashMap<String, WeakReference<Class<?>>>();
+    m_useResourceFiltering = SerializationUtility.isUseBundleOrderPrefixListAsResourceFilterEnabled();
+    m_useResourceCaching = SerializationUtility.isResourceUrlCachingInBundleListClassLoaderEnabled();
+    m_resourceCache = new HashMap<String, Vector<URL>>();
   }
 
-  private Class<?> putInCache(String name, Class<?> c) {
-    if (c != null) {
-      synchronized (m_classCache) {
-        m_classCache.put(name, c);
-      }
+  public Bundle[] getBundles() {
+    return m_bundles;
+  }
+
+  public Bundle[] getBundlesSortedByBundleSymbolicNameLenght() {
+    return m_bundlesSortedByBundleSymbolicNameLenght;
+  }
+
+  public ClassLoader getParentContextClassLoader() {
+    return m_parentContextClassLoader;
+  }
+
+  public boolean isUseResourceFilteringEnabled() {
+    return m_useResourceFiltering;
+  }
+
+  public boolean isUseResourceCachingEnabled() {
+    return m_useResourceCaching;
+  }
+
+  protected Class<?> putInCache(String name, Class<?> c) {
+    m_cacheLock.writeLock().lock();
+    try {
+      m_classCache.put(name, new WeakReference<Class<?>>(c));
+    }
+    finally {
+      m_cacheLock.writeLock().unlock();
     }
     return c;
   }
 
-  private void putInCache(String className) {
-    synchronized (m_classCache) {
-      m_classCache.put(className, null);
+  protected URL putInCache(String name, URL resources) {
+    Vector<URL> urlList = new Vector<URL>();
+    urlList.add(resources);
+    urlList = putInCache(name, urlList);
+    return urlList.firstElement();
+  }
+
+  protected Vector<URL> putInCache(String name, Vector<URL> resources) {
+    if (isUseResourceCachingEnabled()) {
+      m_cacheLock.writeLock().lock();
+      try {
+        m_resourceCache.put(name, resources);
+      }
+      finally {
+        m_cacheLock.writeLock().unlock();
+      }
+    }
+    return resources;
+  }
+
+  protected Vector<URL> getFromCache(String name) {
+    if (!isUseResourceCachingEnabled()) {
+      return null;
+    }
+    m_cacheLock.readLock().lock();
+    try {
+      Vector<URL> ref = m_resourceCache.get(name);
+      return ref;
+    }
+    finally {
+      m_cacheLock.readLock().unlock();
     }
   }
 
-  public void clearCaches() {
-    synchronized (m_classCache) {
+  protected void clearCaches() {
+    m_cacheLock.writeLock().lock();
+    try {
       m_classCache.clear();
+      m_resourceCache.clear();
+    }
+    finally {
+      m_cacheLock.writeLock().unlock();
     }
   }
 
   @Override
   public Class<?> loadClass(String className) throws ClassNotFoundException {
+    if (!registerLoadingItem(className)) {
+      throw new ClassNotFoundException(className);
+    }
+    try {
+      return doLoadClass(className);
+    }
+    finally {
+      unregisterLoadingItem(className);
+    }
+  }
+
+  protected Class<?> doLoadClass(String className) throws ClassNotFoundException {
     // 1. check primitive classes
     Class<?> c = PRIMITIVE_TYPES.get(className);
     if (c != null) {
@@ -130,24 +258,31 @@
 
     // 3. delegate classes starting with 'java.' to parent class loader
     if (className.startsWith("java.")) {
-      return getParent().loadClass(className);
+      return getParentContextClassLoader().loadClass(className);
     }
 
     // 4. check if class is already in the cache
-    synchronized (m_classCache) {
-      c = m_classCache.get(className);
-      if (c != null) {
-        return c;
+    m_cacheLock.readLock().lock();
+    try {
+      WeakReference<Class<?>> ref = m_classCache.get(className);
+      if (ref != null) {
+        c = ref.get();
+        if (c != null) {
+          return c;
+        }
       }
       if (m_classCache.containsKey(className)) {
         throw new ClassNotFoundException(className);
       }
     }
+    finally {
+      m_cacheLock.readLock().unlock();
+    }
 
     Set<Bundle> usedBundles = new HashSet<Bundle>();
 
     // 5. search in best matching bundles based on class and bundle symbolic name
-    for (Bundle bundle : m_bundlesSortedByBundleSymbolicNameLenght) {
+    for (Bundle bundle : getBundlesSortedByBundleSymbolicNameLenght()) {
       if (usedBundles.contains(bundle)) {
         continue;
       }
@@ -164,7 +299,7 @@
     }
 
     // 6. search in active bundles
-    for (Bundle bundle : m_bundlesSortedByBundleSymbolicNameLenght) {
+    for (Bundle bundle : getBundlesSortedByBundleSymbolicNameLenght()) {
       if (usedBundles.contains(bundle)) {
         continue;
       }
@@ -181,7 +316,7 @@
     }
 
     // 7. search in remaining bundles
-    for (Bundle b : m_bundles) {
+    for (Bundle b : getBundles()) {
       if (usedBundles.contains(b)) {
         continue;
       }
@@ -196,7 +331,8 @@
 
     // 8. try context class loader
     try {
-      c = Class.forName(className);
+      // do not call super.loadClass because it checks the native cache (see eclipse equinox bug 127963)
+      c = getParentContextClassLoader().loadClass(className);
       return putInCache(className, c);
     }
     catch (Exception e) {
@@ -204,22 +340,15 @@
     }
 
     // 9. class not found
-    putInCache(className);
+    putInCache(className, (Class<?>) null);
     throw new ClassNotFoundException(className);
   }
 
   @Override
   public URL getResource(String name) {
-    for (Bundle b : m_bundles) {
-      try {
-        URL url = b.getResource(name);
-        if (url != null) {
-          return url;
-        }
-      }
-      catch (Exception e) {
-        //nop
-      }
+    Enumeration<URL> resources = getResources(name);
+    if (resources != null && resources.hasMoreElements()) {
+      return resources.nextElement();
     }
     return null;
   }
@@ -239,22 +368,216 @@
   }
 
   @Override
-  public Enumeration<URL> getResources(String name) throws IOException {
+  public Enumeration<URL> getResources(String name) {
+    if (!registerLoadingItem(name)) {
+      return null;
+    }
+    try {
+      // 1. check if resource is already in the cache
+      Vector<URL> ref = getFromCache(name);
+      if (ref != null) {
+        return ref.elements();
+      }
+
+      // 2. search in bundles
+      Vector<URL> urlList = searchResourcesInBundles(name);
+
+      // 3. filter resources
+      urlList = filterResources(urlList);
+
+      return putInCache(name, urlList).elements();
+    }
+    finally {
+      unregisterLoadingItem(name);
+    }
+  }
+
+  protected Vector<URL> searchResourcesInBundles(String name) {
     Vector<URL> urlList = new Vector<URL>();
-    for (Bundle b : m_bundles) {
+    for (Bundle b : getBundles()) {
       try {
-        Enumeration<URL> en = b.getResources(name);
-        if (en != null && en.hasMoreElements()) {
-          while (en.hasMoreElements()) {
-            URL url = en.nextElement();
-            urlList.add(url);
-          }
+        Enumeration resources = b.getResources(name);
+        while (resources != null && resources.hasMoreElements()) {
+          URL url = (URL) resources.nextElement();
+          urlList.add(url);
         }
       }
       catch (Exception e) {
         //nop
       }
     }
-    return urlList.elements();
+    return urlList;
+  }
+
+  protected Vector<URL> filterResources(Vector<URL> urlList) {
+    if (isUseResourceFilteringEnabled()) {
+      Vector<URL> newUrlList = new Vector<URL>();
+      Vector<URL> customUrlList = new Vector<URL>();
+      Enumeration<URL> resources = urlList.elements();
+      while (resources != null && resources.hasMoreElements()) {
+        URL resource = resources.nextElement();
+        newUrlList.add(resource);
+        if (isUrlFromBundlePrefixes(resource)) {
+          customUrlList.add(resource);
+        }
+      }
+      if (!customUrlList.isEmpty()) {
+        urlList = customUrlList;
+      }
+      else {
+        urlList = newUrlList;
+      }
+    }
+    return urlList;
+  }
+
+  protected boolean registerLoadingItem(String name) {
+    Set<String> loadingItems = LOOP_DETECTOR.get();
+    if (loadingItems != null && loadingItems.contains(name)) {
+      return false;
+    }
+
+    if (loadingItems == null) {
+      loadingItems = new HashSet<String>(3);
+      LOOP_DETECTOR.set(loadingItems);
+    }
+    loadingItems.add(name);
+    return true;
+  }
+
+  protected void unregisterLoadingItem(String name) {
+    // invariant: register has already been invoked
+    LOOP_DETECTOR.get().remove(name);
+  }
+
+  /**
+   * Checks if the given string is included in the list of include patterns and that it is not excluded by the list of
+   * exclude patterns. If the include or exclude pattern list is null or empty, the string is assumed to be included and
+   * not excluded, respectively.
+   */
+  private static boolean accept(String s, Pattern[] includePatterns, Pattern[] excludePatterns) {
+    if (s == null) {
+      return false;
+    }
+    boolean included = true;
+    boolean excluded = false;
+    if (includePatterns != null && includePatterns.length > 0) {
+      included = false;
+      for (Pattern p : includePatterns) {
+        if (p.matcher(s).matches()) {
+          included = true;
+          break;
+        }
+      }
+    }
+    if (included && excludePatterns != null && excludePatterns.length > 0) {
+      for (Pattern p : excludePatterns) {
+        if (p.matcher(s).matches()) {
+          excluded = true;
+          break;
+        }
+      }
+    }
+    return included && !excluded;
+  }
+
+  /**
+   * Parses a comma-separated list of filter patterns. A filter pattern is either a wildcard pattern or a regular
+   * expression. Latter must be prefixed by <em>regex:</em>
+   */
+  private static Pattern[] parseFilterPatterns(String filter) {
+    if (filter == null) {
+      return null;
+    }
+    List<Pattern> patterns = new ArrayList<Pattern>();
+    for (String f : filter.split(",")) {
+      f = f.trim();
+      if (f.length() > 0) {
+        try {
+          f = toRegexPattern(f);
+          Pattern pattern = Pattern.compile(f);
+          patterns.add(pattern);
+        }
+        catch (Exception e) {
+          System.err.println("invalid filter pattern: " + e);
+        }
+      }
+    }
+    if (patterns.isEmpty()) {
+      return null;
+    }
+    return patterns.toArray(new Pattern[patterns.size()]);
+  }
+
+  /**
+   * Transforms the given string into a regular expression pattern. The string is assumed to be a wildcard pattern or
+   * already a regular expression pattern. The latter must be prefixed by <em>regex:</em>.
+   */
+  private static String toRegexPattern(String s) {
+    if (s == null) {
+      return null;
+    }
+    String pattern = s.trim();
+    if (pattern.startsWith(REGEX_MARKER)) {
+      return pattern.substring(REGEX_MARKER.length());
+    }
+    pattern = pattern.replaceAll("[.]", "\\\\.");
+    pattern = pattern.replaceAll("[*]", ".*");
+    pattern = pattern.replaceAll("[?]", ".");
+    return pattern;
+  }
+
+  /**
+   * return true if resource {@link URL} is located in a bundle from the list of bundleOrderPrefixes
+   */
+  protected boolean isUrlFromBundlePrefixes(URL resource) {
+    if (m_bundleOrderPrefixes == null) {
+      m_bundleOrderPrefixes = SerializationUtility.getBundleOrderPrefixes();
+    }
+    long bundleID = getBundleID(resource.getHost());
+    if (bundleID >= 0) {
+      Bundle bundle = getBundle(bundleID);
+      if (bundle != null) {
+        for (String bundlePrefix : m_bundleOrderPrefixes) {
+          if (StringUtility.contains(bundle.getSymbolicName(), bundlePrefix)) {
+            return true;
+          }
+        }
+      }
+    }
+    return false;
+  }
+
+  /**
+   * find the bundleId in the host string.
+   * Example: 80.2hwhefh29:3
+   *
+   * @param host
+   *          from resource {@link URL}
+   * @return bundle id
+   */
+  protected long getBundleID(String host) {
+    int dotIndex = host.indexOf('.');
+    return (dotIndex >= 0 && dotIndex < host.length() - 1) ? Long.parseLong(host.substring(0, dotIndex)) : -1;
+  }
+
+  /**
+   * find the {@link Bundle} from a bundle id
+   *
+   * @param id
+   *          bundle id
+   * @return the corresponding {@link Bundle}
+   */
+  protected Bundle getBundle(long id) {
+    BundleContext bundleContext = FrameworkUtil.getBundle(this.getClass()).getBundleContext();
+    Bundle result = null;
+    for (Bundle candidate : bundleContext.getBundles()) {
+      if (candidate.getBundleId() == id) {
+        if (result == null || result.getVersion().compareTo(candidate.getVersion()) < 0) {
+          result = candidate;
+        }
+      }
+    }
+    return result;
   }
 }
diff --git a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleObjectInputStream.java b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleObjectInputStream.java
index 3b556ca..d30e2d0 100644
--- a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleObjectInputStream.java
+++ b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/osgi/BundleObjectInputStream.java
@@ -31,7 +31,7 @@
   private final IObjectReplacer m_objectReplacer;
 
   public BundleObjectInputStream(InputStream in, Bundle[] bundleList) throws IOException {
-    this(in, new BundleListClassLoader(bundleList), null);
+    this(in, new BundleListClassLoader(null, bundleList), null);
   }
 
   public BundleObjectInputStream(InputStream in, ClassLoader classLoader) throws IOException {
diff --git a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/BundleObjectSerializerFactory.java b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/BundleObjectSerializerFactory.java
index 35ddd0d..943c34b 100644
--- a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/BundleObjectSerializerFactory.java
+++ b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/BundleObjectSerializerFactory.java
@@ -10,9 +10,14 @@
  ******************************************************************************/
 package org.eclipse.scout.commons.serialization;
 
+import org.eclipse.scout.commons.internal.Activator;
+import org.eclipse.scout.commons.logger.IScoutLogger;
+import org.eclipse.scout.commons.logger.ScoutLogManager;
 import org.eclipse.scout.commons.osgi.BundleInspector;
 import org.eclipse.scout.commons.osgi.BundleListClassLoader;
 import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 
 /**
  * Factory for creating {@link BundleObjectSerializer} instances. All of them are sharing the very same
@@ -22,12 +27,16 @@
  */
 public class BundleObjectSerializerFactory implements IObjectSerializerFactory {
 
+  private static final IScoutLogger LOG = ScoutLogManager.getLogger(BundleObjectSerializerFactory.class);
+  private static final String CONTEXT_FINDER_CLASS_NAME = "org.eclipse.core.runtime.internal.adaptor.ContextFinder";
+
   private BundleListClassLoader m_bundleBasedClassLoader;
 
   public BundleObjectSerializerFactory() {
     String[] bundleOrderPrefixes = SerializationUtility.getBundleOrderPrefixes();
     Bundle[] orderedBundleLists = BundleInspector.getOrderedBundleList(bundleOrderPrefixes);
-    m_bundleBasedClassLoader = new BundleListClassLoader(orderedBundleLists);
+    ClassLoader parent = getOsgiParentClassLoader();
+    m_bundleBasedClassLoader = new BundleListClassLoader(parent, orderedBundleLists);
   }
 
   @Override
@@ -39,4 +48,38 @@
   public ClassLoader getClassLoader() {
     return m_bundleBasedClassLoader;
   }
+
+  protected ClassLoader getOsgiParentClassLoader() {
+    ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+    if (contextClassLoader != null && CONTEXT_FINDER_CLASS_NAME.equals(contextClassLoader.getClass().getName())) {
+      return contextClassLoader.getParent();
+    }
+
+    // find OSGi parent classloader by service
+    ClassLoader osgiContextClassLoader = null;
+    try {
+      BundleContext context = Activator.getDefault().getBundle().getBundleContext();
+      for (ServiceReference ref : context.getServiceReferences(ClassLoader.class.getName(), null)) {
+        if ("contextClassLoader".equals(ref.getProperty("equinox.classloader.type"))) {
+          try {
+            osgiContextClassLoader = (ClassLoader) context.getService(ref);
+            break;
+          }
+          finally {
+            context.ungetService(ref);
+          }
+        }
+      }
+    }
+    catch (Throwable t) {
+      LOG.error("cannot determine OSGi context class loader", t);
+    }
+    if (osgiContextClassLoader == null) {
+      return null;
+    }
+    if (CONTEXT_FINDER_CLASS_NAME.equals(osgiContextClassLoader.getClass().getName())) {
+      return osgiContextClassLoader.getParent();
+    }
+    return osgiContextClassLoader;
+  }
 }
diff --git a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/SerializationUtility.java b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/SerializationUtility.java
index 34ec2d8..a0b2a66 100644
--- a/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/SerializationUtility.java
+++ b/org.eclipse.scout.commons/src/org/eclipse/scout/commons/serialization/SerializationUtility.java
@@ -20,6 +20,7 @@
 import org.eclipse.scout.commons.internal.Activator;
 import org.eclipse.scout.commons.logger.IScoutLogger;
 import org.eclipse.scout.commons.logger.ScoutLogManager;
+import org.eclipse.scout.commons.osgi.BundleListClassLoader;
 
 /**
  * Utility for serializing and deserializing java objects. The utility works in a standard java environment as well as
@@ -39,13 +40,16 @@
  * following fully qualified class name:
  * <p/>
  * <code>org.eclipse.scout.commons.serialization.CustomObjectSerializerFactory</code>
- * 
+ *
  * @since 3.8.2
  */
 public final class SerializationUtility {
+  private static final IScoutLogger LOG;
 
   public static final String BUNDLE_ORDER_PREFIX_PROPERTY_NAME = "org.eclipse.scout.commons.serialization.bundleOrderPrefixes";
-  private static final IScoutLogger LOG;
+  public static final String ENABLE_RESOURCE_URL_CACHING_PROPERTY_NAME = "org.eclipse.scout.commons.serialization.enableResourceUrlCaching";
+  public static final String ENABLE_USAGE_OF_BUNDLE_ORDER_PREFIX_LIST_AS_RESOURCE_FILTER_PROPERTY_NAME = "org.eclipse.scout.commons.serialization.enableUsageOfBundleOrderPrefixListAsResourceFilter";
+
   private static final IObjectSerializerFactory FACTORY;
 
   static {
@@ -133,8 +137,55 @@
   }
 
   /**
+   * The {@link BundleListClassLoader} can cache the resources URLs it finds in the getResource(s) methods. This has the
+   * potential problem, that when bundles get unloaded, the cached resource URLs can't be found anymore. Instead of
+   * building a big check around this, it was safer to only let it enable if this should never be the case.
+   * </br>
+   * This method returns the value as it is configured in the config.ini file or as system property:
+   * <p/>
+   * <code>org.eclipse.scout.commons.serialization.enableResourceUrlCaching</code>
+   * <p/>
+   */
+  public static boolean isResourceUrlCachingInBundleListClassLoaderEnabled() {
+    String cacheEnabled = null;
+    if (Activator.getDefault() != null) {
+      cacheEnabled = Activator.getDefault().getBundle().getBundleContext().getProperty(ENABLE_RESOURCE_URL_CACHING_PROPERTY_NAME);
+    }
+    if (!StringUtility.hasText(cacheEnabled)) {
+      cacheEnabled = System.getProperty(ENABLE_RESOURCE_URL_CACHING_PROPERTY_NAME, null);
+    }
+    return StringUtility.parseBoolean(cacheEnabled, false);
+  }
+
+  /**
+   * The methods getResource(s) from {@link BundleListClassLoader} return resources from every bundle from the product
+   * plus from the general classpath. There are cases (library bundles with multiple jars) which this behavior has to be
+   * changed. With this property resources will be filtered the following way:
+   * <ul>
+   * <li>if a resource is found in a bundle which matches the prefix list just this or these resources URLs will be
+   * returned.</li>
+   * <li>if no resource is found in a bundle which matches the prefix list the list will be returned unfiltered.</li>
+   * </ul>
+   * </br>
+   * This method returns the value as it is configured in the config.ini file or as system property:
+   * <p/>
+   * <code>org.eclipse.scout.commons.serialization.enableUsageOfBundleOrderPrefixListAsResourceFilter</code>
+   * <p/>
+   */
+  public static boolean isUseBundleOrderPrefixListAsResourceFilterEnabled() {
+    String cacheEnabled = null;
+    if (Activator.getDefault() != null) {
+      cacheEnabled = Activator.getDefault().getBundle().getBundleContext().getProperty(ENABLE_USAGE_OF_BUNDLE_ORDER_PREFIX_LIST_AS_RESOURCE_FILTER_PROPERTY_NAME);
+    }
+    if (!StringUtility.hasText(cacheEnabled)) {
+      cacheEnabled = System.getProperty(ENABLE_USAGE_OF_BUNDLE_ORDER_PREFIX_LIST_AS_RESOURCE_FILTER_PROPERTY_NAME, null);
+    }
+    return StringUtility.parseBoolean(cacheEnabled, false);
+  }
+
+  /**
    * Uses a {@link IObjectSerializerFactory} for creating a new {@link IObjectSerializer}.
-   * 
+   *
    * @return Returns a new {@link IObjectSerializer}.
    */
   public static IObjectSerializer createObjectSerializer() {
@@ -144,7 +195,7 @@
   /**
    * Uses a {@link IObjectSerializerFactory} for creating a new {@link IObjectSerializer} which uses the given
    * {@link IObjectReplacer} for substituting objects during the serializing and deserializing process.
-   * 
+   *
    * @return Returns a new {@link IObjectSerializer}.
    */
   public static IObjectSerializer createObjectSerializer(IObjectReplacer objectReplacer) {
diff --git a/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/DefaultTransactionDelegate.java b/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/DefaultTransactionDelegate.java
index cfa3663..25c3484 100644
--- a/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/DefaultTransactionDelegate.java
+++ b/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/DefaultTransactionDelegate.java
@@ -19,6 +19,7 @@
 import org.eclipse.scout.commons.exception.VetoException;
 import org.eclipse.scout.commons.logger.IScoutLogger;
 import org.eclipse.scout.commons.logger.ScoutLogManager;
+import org.eclipse.scout.commons.serialization.SerializationUtility;
 import org.eclipse.scout.rt.server.admin.inspector.CallInspector;
 import org.eclipse.scout.rt.server.admin.inspector.ProcessInspector;
 import org.eclipse.scout.rt.server.admin.inspector.SessionInspector;
@@ -173,20 +174,7 @@
     ServiceTunnelResponse serviceRes = null;
     try {
       //do checks
-      Class<?> serviceInterfaceClass = null;
-      for (Bundle b : m_loaderBundles) {
-        try {
-          serviceInterfaceClass = b.loadClass(serviceReq.getServiceInterfaceClassName());
-          break;
-        }
-        catch (ClassNotFoundException e) {
-          // nop
-        }
-      }
-      //check access: existence
-      if (serviceInterfaceClass == null) {
-        throw new ClassNotFoundException(serviceReq.getServiceInterfaceClassName());
-      }
+      Class<?> serviceInterfaceClass = SerializationUtility.getClassLoader().loadClass(serviceReq.getServiceInterfaceClassName());
       //check access: service proxy allowed
       Method serviceOp = ServiceUtility.getServiceOperation(serviceInterfaceClass, serviceReq.getOperation(), serviceReq.getParameterTypes());
       checkRemoteServiceAccessByInterface(serviceInterfaceClass, serviceOp, serviceReq.getArgs());
diff --git a/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/ServerJob.java b/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/ServerJob.java
index dee8b27..9e6ab7e 100644
--- a/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/ServerJob.java
+++ b/org.eclipse.scout.rt.server/src/org/eclipse/scout/rt/server/ServerJob.java
@@ -25,11 +25,14 @@
 import org.eclipse.core.runtime.NullProgressMonitor;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.scout.commons.LocaleThreadLocal;
+import org.eclipse.scout.commons.StringUtility;
 import org.eclipse.scout.commons.exception.ProcessingException;
 import org.eclipse.scout.commons.exception.ProcessingStatus;
 import org.eclipse.scout.commons.job.JobEx;
 import org.eclipse.scout.commons.logger.IScoutLogger;
 import org.eclipse.scout.commons.logger.ScoutLogManager;
+import org.eclipse.scout.commons.serialization.SerializationUtility;
+import org.eclipse.scout.rt.server.internal.Activator;
 import org.eclipse.scout.rt.server.transaction.BasicTransaction;
 import org.eclipse.scout.rt.server.transaction.ITransaction;
 import org.eclipse.scout.rt.server.transaction.internal.ActiveTransactionRegistry;
@@ -42,9 +45,12 @@
 public abstract class ServerJob extends JobEx implements IServerSessionProvider {
   private static final IScoutLogger LOG = ScoutLogManager.getLogger(ServerJob.class);
 
-  private IServerSession m_serverSession;
+  //use classloader from SerializationUtility in Server Job
+  private static final String customClassloaderProperty = "org.eclipse.scout.rt.server.customServerJobClassloader";
+  private final IServerSession m_serverSession;
   private Subject m_subject;
   private long m_transactionSequence;
+  private final boolean m_useCostomClassLoader;
 
   /**
    * Perform a transaction on a {@link IServerSession} within a security {@link Subject} (optional)<br>
@@ -71,6 +77,16 @@
     }
     m_serverSession = serverSession;
     m_subject = subject;
+    m_useCostomClassLoader = isUseCustomClassloader();
+  }
+
+  private boolean isUseCustomClassloader() {
+    try {
+      return StringUtility.parseBoolean(Activator.getDefault().getBundle().getBundleContext().getProperty(customClassloaderProperty));
+    }
+    catch (Exception e) {
+      return false;
+    }
   }
 
   /**
@@ -192,12 +208,16 @@
     Map<Class, Object> backup = ThreadContext.backup();
     Locale oldLocale = LocaleThreadLocal.get();
     ScoutTexts oldTexts = TextsThreadLocal.get();
+    ClassLoader oldContextClassLoader = Thread.currentThread().getContextClassLoader();
     try {
       ThreadContext.putServerSession(m_serverSession);
       ThreadContext.putTransaction(transaction);
       LocaleThreadLocal.set(m_serverSession.getLocale());
       TextsThreadLocal.set(m_serverSession.getTexts());
       ActiveTransactionRegistry.register(transaction);
+      if (m_useCostomClassLoader) {
+        Thread.currentThread().setContextClassLoader(SerializationUtility.getClassLoader());
+      }
       //
       IStatus status = runTransaction(monitor);
       if (status == null) {
@@ -279,6 +299,7 @@
       }
       LocaleThreadLocal.set(oldLocale);
       TextsThreadLocal.set(oldTexts);
+      Thread.currentThread().setContextClassLoader(oldContextClassLoader);
     }
   }