How to solve external library causing JAR conflicts? - java

I'm using Lucene 5.0 in my application and I'm also using the DISCO java library, which does, in turn, use Lucene 3.5. When I import the DISCO jar I can't run the program anymore, because I get runtime errors regarding Lucene classes that are conflicting between the two versions of the library.
Is there a way to solve this?

Yes. There are three solutions:
You can downgrade your code to use Lucene 3.5
You can upgrade DISCO to use Lucene 5.0 (either yourself or by getting the DISCO team to do it)
You can use several ClassLoaders to isolate the code.
The last point works since two classes in Java are the same if the fully qualified name is the same and when they were loaded by the same ClassLoader.
The OSGi framework can do tricks like this. Eclipse uses Equinox, which is an implementation of OSGi.
One option is to bundle everything and set up OSGi to load things correctly.
To solve your problem yourself, you can create two ClassLoaders. One loads your application and Lucene 5.0. The other loads DISCO and and Lucene 3.5.
The ugly part is that you can now get ClassCastException for classes in Lucene. They will have the same name but they won't be the same as far as Java is concerned (different classloaders). To be able to pass data between the two classloaders, you need a parent ClassLoader which has POJOs in which you can put all the data which you want to share. java.lang.String will also be in this ClassLoader (otherwise, things would be very, very complicated).
Shared dependencies can also go in the parent ClassLoader.
You then need a thin adapter layer on top of the DISCO/Lucene code which allows you to do the operations you want without using any of the classes which this classloader doesn't like.

Related

Consistent OSGi import of 3rd party libraries

I've been developing OSGi modules but so far I've come across a number of issues when I've had to wrap existing jars. An example of this is the use of the Oracle database driver which, even though I've wrapped the jar as bundle, just refuses to work (cannot find the driver class even though its present). This is just a single example but I've had issues with other 3rd party libraries and was wondering if there's a best practice approach to using 3rd party libraries which works every time?
Jlove
The problem in your case is that jdbc uses a class from the java runtime to find the database driver (DriverManager.getConnection). This can not work as the database driver is not accessible from the system classloader (that loaded the DriverManager class).
A way that works in OSGi is to use a DataSource instead: http://docs.oracle.com/javase/tutorial/jdbc/basics/sqldatasources.html . There you simply create the data source using new and this of course works. The problem is that it makes your user bundle depend on the specific DB driver. So the best practice is to create the DataSource centrally and publish it as service.
You can find some more details in my Apache Karaf DB Tutorial (http://www.liquid-reality.de/display/liquid/2012/01/13/Apache+Karaf+Tutorial+Part+6+-+Database+Access).
Btw. In general this kind of factories are tpyically where libraries fail in OSGi. Every lib invents another and different factory system and most of the are incompatible with the restricted classloaders of OSGi. Luckily most libs are made OSGi ready nowadays. Most times this simply means that you can also call the factory with a concrete object that you can retrieve using an OSGi service.
My preferred approach is not to wrap the library, but to unjar it, add a manifest, and re-jar it. Jars-inside-jars tend to cause issues that are hard to debug. Unjar and re-jar can be automated with a simple ant script.
Also, I like to write MANIFEST.MF manually. If the library being wrapped is small, then it's easy enough to do that. Tools like bnd that generate MANIFEST.MF for you do not always give the right results, and if you rely on them too much you don't know what is going on under the hood.

Java: Libraries within Libraries and Classpath Issues

We are having a discussion at work and an interesting point came up:
Say you are developing a small library, call it somelib. Say that somelib needs to do some logging, but you don't want to reinvent the wheel, so you decide to use a 3rd party logging library.
Additionally, you want to make integration of somelib as painless as possible, so you distribute a single JAR file (somelib.jar), which has the other logging JAR, call it logger.jar, embedded inside of it. Much like what Maven's jar-with-dependencies assembly does.
Now comes the issue. Since your product is a library, what if your customer is using somelib and also happen to be using a different version of the same logging library on their own. Now we have a classpath problem.
This seems to me like it would be a common problem for people that write libraries, so what is the typical solution?
Do they avoid using JAR bundling methods altogether? Even if we do that, there is still an issue with a user's code expecting version X of the logging library, and somelib's code expecting version Y.
Do they somehow insert a dummy package prefix so that the logger classes in somelib won't conflict?
What about dynamic loading of the logger library? (though this still has versioning problems from 1.)
You may consider to use OSGI or wait for JDK 8 and its Jigsaw project.

JAR Hell Hacks for Non-OSGi Developers

Edit: After reviewing the play, the example I used below is a tad misleading. I am looking for the case where I have two 3rd party jars (not homegrown jars where I have access to the source code) that both depend on different versions of the same jar.
Original:
So I've recently familiarized myself with what OSGi is, and what ("JAR Hell") problems it addresses at its core. And, as intrigued as I am with it (and plan on migrating somewhere down the road), I just don't have it in me to begin learning what it will take to bring my projects over to it.
So, I'm now lamenting: if JAR hell happens to me, how do I solve this sans OSGi?
Obviously, the solution would almost have to involve writing my own ClassLoader, but I'm having a tough time visualizing how that would manifest itself, and more importantly, how that would solve the problem. I did some research and the consensus was that you have to write your own ClassLoader for every JAR you produce, but since I'm already having a tough time seeing that forest through the trees, that statement isn't sinking in with me.
Can someone provide a concrete example of how writing my own ClassLoader would put a band-aid on this gaping wound (I know, I know, the only real solution is OSGi)?
Say I write a new JAR called SuperJar-1.0.jar that does all sorts of amazing stuff. Say my SuperJar-1.0.jar has two other dependencies, Fizz-1.0.jar and Buzz-1.0.jar. Both Fizz and Buzz jars depend on log4j, except Fizz-1.0.jar depends on log4j-1.2.15.jar, whereas Buzz-1.0.jar depends on log4j-1.2.16.jar. Two different versions of the same jar.
How could a ClassLoader-based solution resolve this (in a nutshell)?
If you're asking this question from an "I'm building an app, how do I avoid this" problem rather than a "I need this particular solution" angle, I would strongly prefer the Maven approach - namely, to only resolve a single version of any given dependency. In the case of log4j 1.2.15 -> 1.2.16, this will work fine - you can include only 1.2.16. Since the older version is API compatible (it's just a patch release) it's extremely likely that Fizz 1.0 won't even notice that it's using a newer version than it expected.
You'll find that doing this will probably be way easier to debug issues with (nothing confuses me like having multiple versions of even classes or static fields floating around! Who knows which one you're dealing with!) and doesn't need any clever class loader hacks.
But, this is exactly what all the appservers out there have to deal with. Pretend that your Fizz and Buzz are web applications (WARs), and Super-Jar is you appserver. Super-Jar will arrange a class loader for each web app that "breaks" the normal delegation model, i.e. it will look locally (down) before looking up the hierarchy. Go read about it in any of the appservers's documentation. For example http://download.oracle.com/docs/cd/E19798-01/821-1752/beade/index.html.
Use log4j-1.2.16. It only contains bugfixes wrt 1.2.15.
If Fizz breaks with 1.2.16, fork and patch it, then submit those patches back to the author of Fizz.
The alternative of creating custom classloaders with special delegation logic is very complex and likely to cause you many problems. I don't see why you would want to do this rather than just use OSGi. Have you considered creating an embedded OSGi framework, so you don't have to convert your whole application?

java: use two version of the same lib in one webapp

I'm facing the following problem: I have one module in my webapp that needs jaxb 1.x and the other module needs jaxb 2.x. The first module doesn't work with the new version of jaxb, and the opposite. How can I use these two jars in one project?
Thanks.
For a regular application, usually very different versions use different package names. If this is the case, you can use them both at once without problem. However if they are the same, you can use jarjar to rename the package.
However since you are using a web container each application should use the version you deploy and not the other version. i.e. the web container works it out for you.
OSGi is another container which manages the versions much more explicitly and give you more control over these issues (however I believe you need it just for this)
You have got a jar-hell issue. Generally speaking in normal java environment it's impossible to solve this problem. You have to force modularization into your project by using OSGI. Starting point: http://www.osgi.org/About/HowOSGi
If you are using the JAXB reference implementation, then you can use your JAXB 1 models with the JAXB 2 runtime by including the jaxb1-impl.jar.
http://jaxb.java.net/faq/index.html#running1Apps
As Shaman said is imposible to resolve this issue.
Let's see: the servlet container JRE has only one classloader, and this classloader can load and use one class from jaxb or the other, but not both that will give you a classdefnotfound exception or something similar.
You can not solve this directly:
you can get the code (is opensource) and change the package of one to another name so the classloader can use both. I do not recommend you this solution, is a bad one.
Better is that you migrate the code to use the most modern API (jaxb 2)

What happens when two Java frameworks need third one but each of the two need different version of third one?

In my Java project I'm using two different frameworks (let's say A.jar and B.jar) and both of them require one common framework (let's say Log4j.jar) but in two different versions. How it is treated by Java if framework A needs Log4J v1.1 and B needs Log4j v1.2? Will it cause some kind of conflict/error or will by somehow (how?) resolved?
If it will not cause conflict/error (my project can be compiled and run) - can I use any version of Log4j myself in this project? Or am I forced to select lower / higher version number of Log4j?
Update: to be more specific...
What if some part of Log4j API changed in v1.2 (let's say one single method doIt() signature changed) and both A and B call doIt. What will happend? Will my project run? Will it crash on first use of doIt? What version of Log4j I must put on classpath - v1.2 or both?
In a flat class loading scheme, there is no library versioning support. You need to use the most recent version of the library and remove other versions from the classpath.
Some frameworks, like OSGi, provide mechanisms for handling these cases, but it isn't clear that you're relying on a plug-in framework.
Edit:
What if some part of Log4j API changed in v1.2 (let's say one single method doIt() signature changed) and both A and B call doIt. What will happen? Will my project run? Will it crash on first use of doIt?
A call relying on a signature that is not present will likely result in a NoSuchMethodError. This probably will not happen until the method is invoked. Other errors may occur if other mechanisms rely on the signature (e.g. class inheritance).
What version of Log4j I must put on classpath - v1.2 or both?
Making two versions of a library on the classpath will just result in one set of classes being loaded randomly. The behaviour will be undefined, but could potentially result in all sorts of unpleasant errors and exceptions.
Java doesn't natively support the management of multiple versions of the same piece of code, that is, you can only use at most one version within the same JVM (with the default class loader). However, checkout question 1705720, which has several answers pointing out possible ways of achieving that (OSGi or custom class loaders).
But I doubt it worth the trouble as multiple log4j versions are not required by your code directly. In you case, I'd suggest to start from using the newer log4j version (v1.2) first and verify if it would cause any problem for framework A. If it does cause conflict, then fall-back to log4j v1.1 and verify again. If you really are out of luck, then you need to get your hands dirty...
Update: for you specific description, there's no way of using either log4j v1.1 or v1.2, as framework A and B each require different signature. You have to roll your own version of any of log4j or framework A, or B.
If your lucky picking one version of the third party jar will just work. In the end one needs a container that understands and enables versioning of components all to coexist, something like OSGI.
Although not recommended but you can use both versions. Put each version in a place where only that framework can see.
The optimal solution will be to get the last versions of both A and B, where both use the last common libraries

Categories

Resources