2

I have plug-in A which references a 3rd party JAR. I'm trying to dynamically load classes from this JAR from plug-in B.

I have something like this from a class in plug-in B:

myClass = getClass().getClassLoader().loadClass("com.foo.Bar");

This works fine if I'm trying to load classes that are defined in plug-in A, but I get ClassNotFoundException when trying to load classes in the JAR referenced in plug-in A.

The JAR is on the class-path and is accessible when I'm not trying to dynamically load via this class loader.

Any ideas?

Thanks in advance.

lucks
  • 936
  • 2
  • 10
  • 23

3 Answers3

1

I think the key to your solution is the following I read in another post, Each Eclipse Plugin maintains it's own classloader.

So I can tell you what my Eclipse (3.7) plugin is doing for dynamic loading, I'm not sure if you can "fit" it into your needs. Basically, my plugin does encryption/decryption. As a new enhancement, I allow the user to load one or more Jar files via a FileDialog, where each jar file is loaded using a URLClassLoader. I create my "custom" classloader with the following line :

this.FQCNLoader = new URLClassLoader ( getFQCNUrls(), this.getClass ().getClassLoader () );

The key was to pass in the Eclipse plugin's ClassLoader as the second argument...This means your "custom" classloader has the CLASSPATH of the Eclipse plugin plus whatever URLs you are adding (the getter getFQCNUrls() returns a URL[]).

The other key piece, was I maintain the "custom" classloader in a View that I know persists for the entire life of the Eclipse plugin. The View resides above all code that needs to reference the dynamic *.jar classes.

So, you'll need to do something along the same lines. Your "custom" classloader would need to live in a Class above both pluginA and pluginB. Also, i would think because each plugin has it's own classloader, you're actually going to need to build a "custom" classloader that does something like the following :

1.) Let pluginB load dynamic *.jar/classes.  Convert into URL[].
2.) Create "custom" loader, pass in pluginB URL[] PLUS pluginA classloader.

Here is the addURL() method in my View class that builds the "custom" classloader...it's a little ugly, because I wrote it in a hurry and it rebuilds the "custom" classloader each time the user loads a *.jar, but it does work in Eclipse and without OSGi :

private int             URLCount = 0; 
private URL[]           FQCNUrls = new URL[10];
private ClassLoader     FQCNLoader = new URLClassLoader ( this.FQCNUrls );

...Lots of code...

public  void addURL ( URL theURL ) throws IOException {
    //CPTest  lclsTest = new CPTest();
    if ( getURLCount () == 0 ) { 
        getFQCNUrls()[getURLCount()] = theURL;
        setURLCount ( 1 );
    }
    else { 
        boolean lisThere = false;
        for (int i = 0; i < getURLCount(); i++) {
            if (getFQCNUrls()[i].toString().equalsIgnoreCase(theURL.toString())) {
                lisThere = true;
            }
        }
        if ( lisThere ) { 
            System.out.println ( "File URL [" + theURL.toString () + "] already on the CLASSPATH!" );
        }
        else { 
            getFQCNUrls()[getURLCount()] = theURL;
            setURLCount ( getURLCount ()+1 );
        }

    }

    // CLEAR : Null out the classloader...
    this.FQCNLoader = null;
    // BUILD/RE-BUILD : the classloader...
    this.FQCNLoader = new URLClassLoader ( getFQCNUrls(), this.getClass ().getClassLoader () );

    //try {
    //    System.out.println ( "---------------------------------------------------------------------------" );
    //    System.out.println ( "Current Working Directory.............[" + System.getProperty ( "user.dir" ) + "]" );
    //    System.out.println ( "---------------------------------------------------------------------------" );
    //    System.out.println ( "this.classes []! " );
    //    lclsTest.dumpClasses ( this.getClass ().getClassLoader () );
    //    System.out.println ( "---------------------------------------------------------------------------" );
    //
    //    System.out.println ( "---------------------------------------------------------------------------" );
    //    System.out.println ( "File URL [" + theURL.toString () + "] added! " );
    //    lclsTest.dumpClasses ( this.FQCNLoader );
    //    System.out.println ( "---------------------------------------------------------------------------" );

    //    System.out.println ( "---------------------------------------------------------------------------" );
    //    System.out.println ( "ClassLoader.getSystemClassLoader() " );
    //    lclsTest.dumpClasses ( ClassLoader.getSystemClassLoader() );
    //    System.out.println ( "---------------------------------------------------------------------------" );

    //    Class  cls = this.FQCNLoader.loadClass ( "com.lmig.RRFECF.pso.security.nonproduction.CM_RRFECF_development_securitykey" );
    //  Class  cls = Class.forName(theFQCN, false, theClsLoader);
    //    theObjectKey = ( Object )  theClsLoader.loadClass(theFQCN);


    //}
    //catch ( Exception e ) {
    //// TODO Auto-generated catch block
    //    e.printStackTrace();
    //}

    Class sysclass = URLClassLoader.class;
    try {
        Method method = sysclass.getDeclaredMethod("addURL", parameters);
        method.setAccessible(true);
        method.invoke(getFQCNLoader(), new Object[] {
            theURL
        });
    }
    catch (Throwable t) {
        t.printStackTrace();
        throw new IOException(
            "Error, could not add URL to system classloader");
    }
}

BTW, I encourage you to grab that CPTest class commented out in the first line, it was invaluable to me on getting insight into the different classloaders and what was in them. I left the Sysout lines in the code so you can see how to dump all the classes in a classloader...

Community
  • 1
  • 1
lincolnadym
  • 909
  • 1
  • 12
  • 29
1

You got your setup wrong. Ideally the 3rd party jar should be created as a plugin (File->New->Plugin from existing jars), exposing the classes in the jar. Then set both A & B to depend on it.

Prakash G. R.
  • 4,746
  • 1
  • 24
  • 35
  • You're probably right, but this isn't a viable option in our application. For my problem, I've decided against using reflection to dynamically instantiate classes and going with code generation instead. – lucks Apr 06 '11 at 21:31
0

In plugin A, make sure that you've added the jar to bundle manifest classpath and then make sure to export the packages contained in that jar.

Open META-INF/MANIFEST-MF file. You should see a multi-page editor. Go to Runtime tab and see Classpath section. Then see Exported Packages section. Then go to Build page and make sure that your jar is checked under Binary Build section.

Konstantin Komissarchik
  • 28,879
  • 6
  • 61
  • 61
  • Yup, it's all there. The setup works under normal circumstances... I just run into issues when dynamically loading classes at runtime with the Bundle classloader – lucks Apr 06 '11 at 21:29
  • @lucks: you and everyone else :-/ Ever tried using a 3rd-party JDBC driver directly, without going through the Eclipse framework? I still shudder when I think of the various hacks I had to employ to do this... – thkala Apr 06 '11 at 23:21
  • If the bundle classloader isn't working, you didn't get one of the settings correct. Double check all those sections that I referenced. – Konstantin Komissarchik Apr 07 '11 at 00:53
  • Yes, I've verified the settings you've listed. I can reference those classes just fine everywhere else in my application. Like I said, I only run into problems when trying to dynamically load classes using the method described in my question. – lucks Apr 07 '11 at 16:13
  • In that case, you are maybe accessing the wrong classloader. Does the class where you make this invocation from reside in the same bundle as the class you are trying to load? – Konstantin Komissarchik Apr 07 '11 at 16:41