Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ContextFinder falls back to context classloader resulting in class cast errors #121

Open
haster opened this issue Dec 23, 2019 · 0 comments
Open

Comments

@haster
Copy link

@haster haster commented Dec 23, 2019

When we call JAXBContext.newInstance(Class<?>...) or JAXBContext.newInstance(Class<?>[], Map<String,?>) to create a new JAXBContext we can't pass in a classloader. This then in return means that ContextFinder.newInstance gets called with the context classloader.

Given that the context classloader can be "higher" in the chain of classloaders than the current class's classloader and that, since the jaxb-api is no longer part of java core, this might mean that the JAXBContext class has already been loaded (for instance, in the current class's classloader) in a way that makes it inaccessible to the current thread's context classloader.

This means that if we end up at line 362 and 363 of ContextFinder.find(Class<?>[], Map<String, ?>) and then eventually at line 230 where we will load the factory class using the context classloader

spi = ServiceLoaderUtil.safeLoadClass(className, ModuleUtil.DEFAULT_FACTORY_CLASS, getContextClassLoader());

we might end up with a JAXBContextImpl whose class is loaded on the context clasloader and which implements JAXBContext whose class is loaded on the context classloader and which therefore is not an instance of the JAXBContext whose class got loaded on the current class's classloader!
This in return means that the instanceof check on line 252 of ContextFinder would fail since the returned JAXBContextImpl is not an instance of the JAXBContext the ContextFinder knows.

I ran into this issue while using javax.xml.bind:jaxb-api:2.3.1 on JDK 11 (specifically, while trying to read a persistence.xml using the hibernate jpa metamodel generator through the maven processor plugin). Some more detail on the hibernate jira HHH-13794 and in my blog post.

I ended up with a stacktrace like:

Caused by: javax.xml.bind.JAXBException: ClassCastException: attempting to cast jar:file:/home/haster/.m2/repository/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar!/javax/xml/bind/JAXBContext.class to jar:file:/home/haster/.m2/repository/javax/xml/bind/jaxb-api/2.3.1/jaxb-api-2.3.1.jar!/javax/xml/bind/JAXBContext.class. Please make sure that you are specifying the proper ClassLoader.
at javax.xml.bind.ContextFinder.handleClassCastException(ContextFinder.java:157)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:300)
at javax.xml.bind.ContextFinder.newInstance(ContextFinder.java:286)
at javax.xml.bind.ContextFinder.find(ContextFinder.java:409)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:721)
at javax.xml.bind.JAXBContext.newInstance(JAXBContext.java:662)
at org.hibernate.jpamodelgen.util.xml.XmlParserHelper.getJaxbRoot(XmlParserHelper.java:122)
... 39 more

I see no reason why this wouldn't fail with the current version of jaxb-api but I haven't yet been able to test it. I think this will fail any time where the JAXBContext as used in ContextFinder gets loaded on a classloader that is not the current thread's context classloader.

Constructing a testcase is somewhat less trivial since you have to make sure that the classloaders are configured correctly. I'm fairly certain that the testcase I provided for HHH-13794](https://hibernate.atlassian.net/browse/HHH-13794) would work for (or be easily adapted to) the current jakarta jaxb-api.

Update: I tested the testcase in HHH-13794 against jakarta.xml.bind:jakarta.xml.bind-api:2.3.2 and the error was there as well.

This issue is similar to, though definitely not the same as, #99.

I propose two solutions to mitigate this issue:

  1. Add an API method to construct a JAXBContext with a passed set of classes to be bound while also passing in a ClassLoader to be used to load the context. Something like JAXBContext.newInstance(Class<?>[], Map<String,?>, ClassLoader).

  2. Use a different "default classloader" instead of the current thread's context classloader for all such situations . Maybe just getClass().getClassloader() or JAXBContext.class.getClassLoader() . That last one would at the very least ensure that the instanceof check against JAXBContext succeeds.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
1 participant
You can’t perform that action at this time.