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);
}
}