[rules-users] OSGI, classloading, and imports in KnowledgeBuilder

Faron Dutton fgdutton at gmail.com
Fri Jun 19 07:47:07 EDT 2009


Let me start by saying that I am unfamiliar with Drools Flow but I
encountered a similar issue when using Drools Expert in Eclipse, which is
due to the ClassLoader behavior defined by OSGi v4. Drools attempts to
access a Class through a reference to a ClassLoader, which by default is its
bundle ClassLoader. MVEL attempts to access a Class through the context
ClassLoader, which OSGi sets to the bundle's ClassLoader. The problem arises
because neither the Drools bundle nor the MVEL bundle (I have each Drools
artifact in a separate bundle) declare a dependency on the package
containing your classes. Therefore, OSGi does not add that package to the
bundle's ClassLoader and you get a ClassNotFoundException when Drools
attempts to find the Class.

I worked around this issue by doing several things:
1) Created an implementation of ClassLoader that delegates to the bundle
(BundleClassLoader).
2) Created a composite ClassLoader that contains many bundle ClassLoaders
(CompositeClassLoader).
3) For each bundle containing a rule or fact, wrap it in a BundleClassLoader
and add it to the CompositeClassLoader.
4) Pass the CompositeClassLoader to Drools when creating the
KnowledgeBuilder.
5) Wrap the Drools facades in a Class that sets the context ClassLoader
before each method invocation and restores the original context ClassLoader
afterwards.

This takes care of most but not all of the issues. Specifically, some
invocations of MVEL still fail but the system works. Unfortunately, I have
trouble consistently reproducing the issue so I have been unable to track it
down. It may no longer exist in the latest Drools; I encountered this issue
in the M3 build last December.

I have included my implementation of BundleClassLoader below as well as an
example of a wrapper method:

    @Override
    public ProcessInstance startProcess(final String processId, final
Map<String, Object> parameters) {
        ClassLoader old = Thread.currentThread().getContextClassLoader();
        try {
 
Thread.currentThread().setContextClassLoader(getEngineClassLoader());
            return super.startProcess(processId, parameters);
        } finally {
            Thread.currentThread().setContextClassLoader(old);
        }
    }

The getEngineClassLoader() method returns the CompositeClassLoader, which I
built in the Activator.

----------------------------------------------------------------------------
----------------------------------

/**
 * Wraps a Bundle; making it appear as a ClassLoader.
 * <p/>
 * This is an adaptation of the technique described in the article <i>
 * <a href='http://wiki.eclipse.org/BundleProxyClassLoader_recipe'>
 * BundleProxyClassLoader recipe</a></i>. The main changes were to add
 * comments and declarations for generic types.
 *
 * @author Simon Kaegi
 * @author Faron Dutton
 */
public class BundleProxyClassLoader extends ClassLoader implements
DroolsClassLoader {
    private final Bundle bundle;
        
    /**
     * Initializes a new instance of this class.
     * 
     * @param bundle the bundle to wrap
     * @throws IllegalArgumentException if bundle is <code>null</code>
     */
    public BundleProxyClassLoader(final Bundle bundle) {
        this(bundle, null);
    }
    
    /**
     * Initializes a new instance of this class.
     * 
     * @param bundle the bundle to wrap
     * @param parent the parent class loader. may be <code>null</code>
     * @throws IllegalArgumentException if bundle is <code>null</code>
     */
    public BundleProxyClassLoader(final Bundle bundle, final ClassLoader
parent) {
        super(parent);
        this.bundle = bundle;
        if (this.bundle == null) {
            throw new IllegalArgumentException("bundle cannot be null");
        }
    }

    /* (non-Javadoc)
     * Both ClassLoader.getResources(...) and bundle.getResources(...)
     * consult the boot class loader. As a result, BundleProxyClassLoader.
     * getResources(...) might return duplicate results from the boot
     * class loader. Prior to Java 5 Classloader.getResources was marked
     * final. If your target environment requires at least Java 5 you
     * can prevent the occurrence of duplicate boot class loader resources
     * by overriding ClassLoader.getResources(...) instead of 
     * ClassLoader.findResources(...).
     *    
     * @see java.lang.ClassLoader#findResources(java.lang.String)
     */
    @SuppressWarnings("unchecked")
    protected Enumeration<URL> findResources(final String name) throws
IOException {
        return bundle.getResources(name);
    }

    /* (non-Javadoc)
     * @see java.lang.ClassLoader#findResource(java.lang.String)
     */
    protected URL findResource(final String name) {
        return bundle.getResource(name);
    }

    /* (non-Javadoc)
     * @see java.lang.ClassLoader#findClass(java.lang.String)
     */
    protected Class<?> findClass(final String name) throws
ClassNotFoundException {
        return bundle.loadClass(name);
    }
    
    /* (non-Javadoc)
     * @see java.lang.ClassLoader#getResource(java.lang.String)
     */
    public URL getResource(final String name) {
        return (getParent() == null) ? findResource(name) :
super.getResource(name);
    }

    /* (non-Javadoc)
     * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
     */
    @Override
    public synchronized Class<?> loadClass(final String name, final boolean
resolve) throws ClassNotFoundException {
        Class<?> clazz = (getParent() == null) ? findClass(name) :
super.loadClass(name, false);

        if (resolve) {
            super.resolveClass(clazz);
        }
        
        return clazz;
    }

    /* (non-Javadoc)
     * @see
org.drools.rule.DroolsClassLoader#fastFindClass(java.lang.String)
     */
    public Class<?> fastFindClass(final String name) {
        try {
            return bundle.loadClass(name);
        } catch (ClassNotFoundException e) {
            return null;
        }
    }
}




More information about the rules-users mailing list