Classloader problems!?

Hello,

I'm trying to create an Idea plugin for an app that makes heavy use of the apache xmlbeans libraries.. (SoapUI; http://www.soapui.org). Strangely, I run into problems with xmlbeans which seem to be class-loader related:
- I've put all my jars in a lib directory in my plugin, including all xmlbeans jars and the jars xmlbeans has generated for my xml schemas
- When parsing xml-files with these generated jars, xmlbeans does not find the generated type-information that has been compiled into them unless I explicitely change the current threads context-classloader to the one for my plugin.
-> It seems as if xmlbeans is being instantiated by a classloader that does not have access to these generated jars!? I cant see xmlbeans being included in the idea lib directory (otherwise that would probably explain the problem since the idea classloader wouldn't find my jars.. )

It is not realistic for me to switch contextclassloader every time execution comes into code in my plugin.. how can I work around this?

Ultimately, I would like my plugins ClassLoader to be "reverse".. ie first check in the plugins classpath, then the parent.. (like it is in many j2ee/ear-deployers).. is there an option for this?

(I'm using the 6.0.1 idea release and the latest plugin dev-kit)

Any help is greatly appreciated!

regards,

/Ole
eviware.com

11 comments

The bad news:
You have no other option than to set the current thread's context classloader manually. It's a property of IDEA's plugin mechanism that your plugin classes are loaded with a specific classloader but called with IDEA's default classloader. And there's no possibility for IDEA to change this. Even if it would be technically possible to change the context classloader each time before calling a plugin method (it isn't...), it would be an immense decrease of performance.

The good news:
You won't have to set the context classloader that often. I'm quite sure that there are very few calls which really rely directly on the context classloader. For example when working with JNDI, it's only the call to the constructor of InitialContext which relies on the context classloader. So in fact you will end with some few locations where you really have to set the context classloader.

By the way:
Don't forget to restore the default classloader. For example I'm doing this in some code like this:

0

hmm ok.. thanks for your reply..

As I already mentioned we use XmlBeans extensively (="all the time") and not in a few isolated situations, I'll see if this can be narrowed down somehow.. Since the same code is being used for the standalone swing version of SoapUI I dont want to "litter" it with IntelliJ-specific workarounds..

One easy solution would maybe be to put these jars in the idea/lib folder

regards!

/Ole

0

As I already mentioned we use XmlBeans extensively
(="all the time") and not in a few isolated
situations, I'll see if this can be narrowed down
somehow.. Since the same code is being used for the
standalone swing version of SoapUI I dont want to
"litter" it with IntelliJ-specific workarounds..


I didn't mean to set the context classloader each time you're using XmlBeans. But I'm quite sure that only some few parts of XmlBeans really require the context classloader to be set. As for the JNDI example: you can use JNDI calls all over your project, but only when creating a new InitialContext the classloader has to be set correctly. Most probably it will be similar with XmlBeans.

One easy solution would maybe be to put these jars in
the idea/lib folder !? (although not recommended, I
know)


Don't even think about this That's definitively calling for trouble...

by the way, our eclipse-plugin works in the same way
without any of these problems.. so it cant be that
hard to fix if they did it ;)


I won't comment on eclipse, but there's definitively nothing to 'fix'. The current behavior is intended and there are good reasons for it.

0

Ole Matzura schrieb:

As I already mentioned we use XmlBeans extensively (="all the time")
and not in a few isolated situations, I'll see if this can be narrowed
down somehow.. Since the same code is being used for the standalone
swing version of SoapUI I dont want to "litter" it with
IntelliJ-specific workarounds..


Well, you could surround each method or just those methods you know
might be called from outside with

Thread t = Thread.currentThread();
ClassLoader oldClassLoader = t.setContextClassLoader(...);
try {
callOriginalMethod();
}
finally {
t.setContextClassLoader(oldClassLoader);
}

using AspectJ or something.

by the way, our eclipse-plugin works in the same way without any of
these problems.. so it cant be that hard to fix if they did it ;)


Eclipse uses OSGi and OSGi has a very sophisticated class loader
management.

cu,
Raffi

--
Come to think of it, there are already a million monkeys on a million
typewriters, and Usenet is nothing like Shakespeare!

herzog@raffael.ch · PGP Key 5D1FF5F4 · http://www.raffael.ch/

0

ok thanks.. you are probably right regarding the actual need of the "correct" class-loader..

I'm bumping into more problems though: when using the saxon xpath processor I get "class XXX violates loader constraints" errors; apparently xmlbeans/saxon/etc.. contains som DOM-related classes that already have been loaded by the Intellij classloader.. do I have to manually delete these classes from my dependencies or is there any other workaround?

thanks for any reply!

regards,

/Ole

0

Well, my workaround for a similar problem was to use my own classloader with its own loadClass method. But without more knowledge about your application I can't judge if this would be a suitable for you too.

0

ok thanks..

well basically my plugin is a "repackaging" of soapui, a desktop app for working with web-services. It uses xmlbeans extensively (have I said that before?) to parse/validate/generate xml messages and perform xpath selections.. xmlbeans internally uses the saxon xpath processor.. both the xmlbeans and saxon jars contain some classes that are also available in intellij's own dependencies..

So would a "reverse" classloader (which looks in my own jars before delegating to the parent) maybe solve this if it is set as the contextclassloader around relevant code in my plugin? You mentioned performance issues with switching classloaders?

regards!

/Ole

0

A 'reverse' classloader doesn't solve the problem, as those xmlbeans classes may be already loaded. (By the way: the plugin classloader is already a 'reverse' classloader.) What you need is a classloader which doesn't have IDEA's classloader as its parent. Then those xmlbeans classes are not loaded yet and you get a chance to load them from the correct location. (I can give you an example of such a classloader if you want.)

The problem is that you're working in two different worlds with two different classloader hierarchies then. And you have to decide for every class to which world it belongs. In my case I created a wrapper class which separates those two worlds. The wrapper class is loaded with my special classloader, and then everything accessed from within this wrapper class is loaded automatically from the same classloader. And those few classes from 'outside' which have to be visible inside this hierarchy are explicitely excluded from being loaded by the special classloader.

About performance:
I was only talking about performance issues under the aspect of IDEA switching to the plugin classloader before doing any call into a plugin. But under normal circumstances there's nothing to worry about, as setting the context classloader is a simple assignment.

0

Hi Martin,

I'd be interested in seeing an example classloader that does this since I've
just encountered a similar problem in a plugin I'm writing (java.lang.LinkageError:
loader constraints violated when linking org/w3c/dom/Document class). I can
work around it for now by just using IDEA's libraries instead but I'm not
sure it'll be a good long term solution for me.

Regards,
Chris

A 'reverse' classloader doesn't solve the problem, as those xmlbeans
classes may be already loaded. (By the way: the plugin classloader is
already a 'reverse' classloader.) What you need is a classloader which
doesn't have IDEA's classloader as its parent. Then those xmlbeans
classes are not loaded yet and you get a chance to load them from the
correct location. (I can give you an example of such a classloader if
you want.)

The problem is that you're working in two different worlds with two
different classloader hierarchies then. And you have to decide for
every class to which world it belongs. In my case I created a wrapper
class which separates those two worlds. The wrapper class is loaded
with my special classloader, and then everything accessed from within
this wrapper class is loaded automatically from the same classloader.
And those few classes from 'outside' which have to be visible inside
this hierarchy are explicitely excluded from being loaded by the
special classloader.

About performance:
I was only talking about performance issues under the aspect of IDEA
switching to the plugin classloader before doing any call into a
plugin. But under normal circumstances there's nothing to worry about,
as setting the context classloader is a simple assignment.



0
 excludes = new HashSet();

    /**
     * @param libraries A list of jar files from where classes should be loaded.
     * @param excludes  A set of classes which should be loaded from the original classloader.
     */
    CustomClassLoader(Collection libraries, Collection> excludes) {
        // It is important to pass null as the parent classloader,
        // else the system classloader is set as parent classloader.
        super(new URL[0], null);
        for (File file : libraries) {
            try {
                addURL(file.toURI().toURL());
            } catch (MalformedURLException e) {
                // ignore...
            }
        }
        for (Class> type : excludes) {
            this.excludes.add(type.getName());
        }
    }

    protected Class> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if (name.startsWith("com.intellij.") || excludes.contains(name)) {
            return getClass().getClassLoader().loadClass(name);
        } else {
            return super.loadClass(name, resolve);
        }
    }
}]]>
0

Thanks Martin, that will hopefully save me (and others?) from quite a bit
of pain :)

 
> private final Set excludes = new HashSet();
> 
> /**
> * @param libraries A list of jar files from where classes should
> be loaded.
> * @param excludes  A set of classes which should be loaded from
> the original classloader.
> */
> CustomClassLoader(Collection libraries, Collection
> excludes) {
> // It is important to pass null as the parent classloader,
> // else the system classloader is set as parent classloader.
> super(getUrls(libraries), null);
> this.excludes.addAll(excludes);
> }
> @Override
> 
> @SuppressWarnings({"NonSynchronizedMethodOverridesSynchronizedMethod"}
> )
> protected Class> loadClass(String name, boolean resolve) throws
> ClassNotFoundException {
> return isExcluded(name) ?
> getClass().getClassLoader().loadClass(name) : super.loadClass(name,
> resolve);
> }
> private boolean isExcluded(String name) {
> return name.startsWith("com.intellij.") ||
> excludes.contains(name);
> }
> private static URL[] getUrls(Collection files) {
> return ContainerUtil.map2Array(files, URL.class, new
> Function() {
> @Nullable
> public URL fun(File file) {
> try {
> return file.toURI().toURL();
> } catch (MalformedURLException e) {
> return null;
> }
> }
> });
> }
> }]]>



0

Please sign in to leave a comment.