I just tried testing an application that uses Apache Camel 2.10.3, and immediately, upon the DefaultCamelContext being instantiated, got the following exception:
java.lang.NoSuchMethodError: org.slf4j.Logger.trace(Ljava/lang/String;Ljava/lang/Object;)V
at org.apache.camel.impl.DefaultPackageScanClassResolver.<init>(DefaultPackageScanClassResolver.java:70)
at org.apache.camel.impl.DefaultCamelContext.<init>(DefaultCamelContext.java:222)
I made sure that slf4j-api-1.6.6 (which is what Camel 2.10.3 ships with) was on the runtime classpath. Next, I suspected that I might have other dependencies that also used SLF4J, but that relied on a different version of it. So I opened Eclipse, and ran a type search for org.slf4j.Logger and sure enough, I see that class listed in 2 distinct JARs: slf4j-api-1.6.6.jar (as expected!), and another 3rd party jar, widget-lib-3.0.jar.
So I opened up widget-lib3.0.jar, and see SLF4J packaged up inside of it like so:
widget-lib-3.0/
com/
<Widget Lib's compiled classes>
org/
slf4j/
spi/
...
impl/
...
<A bunch of SLF4J classes, like LoggerFactory.class, etc.>
There's no way to tell what version of SLF4J it's using here, but I'd be willing to bet that it's a version that's earlier than 1.6.x, which is what Camel 2.10.3 wants.
So my best, slightly-educated guess is that at runtime, the JRE classloaders are finding widget-lib-3.0.jar#org/slf4j/Logger first, loading it, and then they go to load the Camel JARs and their dependencies. Then, when DefaultPackageScanClassResolver calls the SLF4J trace(String,Object) method, it's not finding the 1.6.6 version of SLF4J, rather, it's finding whatever version came with widget-lib-3.0.jar, and that method/overload doesn't exist.
Am I on track of way off base? If I'm off base, what does this mean to you, SO? And if I am on track, then my proposed solution would be to re-JAR widget-lib-3.0.jar without the org/slf4j packages in it (no other, more modern versions exist). My theory being that slf4j-api-1.6.6, which is backwards compatible, would be the only SLF4J version that gets loaded, and would then work for both JARs. Any thoughts? Thanks in advance.
Am I on track of way off base?
No. It looks like you are on-track here.
The way to confirm it would be to take the copy of org.sfl4j.Logger in the widget library JAR, and use javah to see if it has the void trace(String, Object) method or not.
Once you have confirmed it, there are a number of solutions:
The cleanest solution would be to get hold of the source code for the widget library, recompile it against the version of sfl4j that you need, and build a new version of the JAR without embedding sfl4j in it. (It is possible that you will need to modify the source of the widget library, but unlikely).
A simpler solution might to make sure that you put the newer (and supposedly backwards compatible) slf4 API JAR ahead of the widget library JAR on the classpath. That way, the old versions of slf4j in the widget JAR will be "shaded" by the newer ones with the extra method that Camel needs.
"There's no way to tell what version of SLF4J it's using here, but I'd
be willing to bet that it's a version that's earlier than 1.6.x, which
is what Camel 2.10.3 wants"...
Why not decompile the class file from the widget-lib-3.0.jar and see if the required method is there or not?
Your approach is the right one. SLF4J 1.x is API-compatible between versions. (Are you using Maven by the way? It's designed to prevent exactly this kind of problem).
What is widget-lib? Is there a version of it that doesn't include its dependencies? If there is, you should use that.
Related
I have a java project where I use an external jar (not controlled by me).
Until now whenever that a new version of that library is out, I update
my project to use the more recent one, but now is required that the
project uses different versions of that library.
My problem is I don't have any clue how to do that.
How do I tell in java to make the imports according a version of a jar,
What I need to do:
int versionFlag = getVersion2use();
if(verssionFlag = 0){
use imports from last version
}else if(verssionFlag = 1){
use imports from last version 1
} else if(verssionFlag = 2){
use imports from last version 2
}
This is to be used at runtime!
This is usually something that a project will do at build time rather than dynamically at runtime.
That said, here's a good answer on how to add a jar to the system classloader dynamically at runtime, which is something you could work into your general logic above:
How should I load Jars dynamically at runtime?
With respect to imports, there's no way around the fact that you can't dynamically pick your imports. So if you're lucky the two versions have the same basic API. If that holds, add the jar to classloader as early as possible in your app and then develop like normal.
If the two versions have different a different API, however, then you're going to have to write some very convoluted code that tries building objects and almost ubiquitously catches all the many different class load / class incompatibility exceptions (such as ClassNotFoundException). Worse, you'll probably have to do this behind some sort of facade or factory architecture so that you can actually keep running software insulated from all these class loading shenanigans. In short, if the two have different APIs you may actually be better off writing two separate products.
At Runtime
Classes with the same name in the same package follow a first available rule. The first one that is on the classpath is the one that is used.
You can not easily do what you want at runtime without a wrapper program to move the libraries into and out of the system classpath before the Java application is started.
A launcher script/program that dynamically builds the classpath and only includes the version you need of each library and passes it to java -cp is the only way to do what you want at runtime.
At build time
If it is at build time, then just use something like the shade plugin in Maven to build an uberjar with all the required libraries embedded in a single .jar for each of the versions. So 3 versions would be 3 separate uberjar assemblies.
If you can do it at build time, you can use a dependency manager, like Maven.
It provides you with a means to be able to select which versions of which library you use at build-time.
If you need to do this at runtime, you might need to package all libraries in your project. You can use shading (see here) to make sure you don't get import issues, because when importing different versions of libs you end up with similar imports.
Shading can help you make for example:
com.foo.Class in jarv1
com.foo.Class in jarv2
To become
com.foo.v1.Class in jarv1
com.foo.v2.Class in jarv2
This will make sure your code can still use all libs you want.
java.util.zip has well-known problems with native memory usage, so i'm trying to use a drop-in replacement called "jazzlib". unfortunately as is typical for sourceforge projects there is no documentation. If I add the jar to my classpath then Java freaks out and gives me "prohibited package name" errors because it replaced java.util.zip. How do I tell Java that this is what I want it to do?
Either add it to the boot class path or add it as an endorsed jar.
Perhaps better, use shade to rename the classes back out of the protected packages, and use them that way. The maven-shade-plugin is convenient if you use maven.
For the latest source, see the classpath CVS repository. On this page you'll find source and binary releases of the code in both the net.sf.jazzlib and java.util.zip namespaces.
Just use the one in net.sf.jazzlib namespace so that you can avoid conflicts.
You can specify overrides for classes provided in the JRE by using the commandline arg -Xbootclasspath. Check this link for more details.
I wouldn't install some random (undocumented) library replacement into my JRE, and certainly I would not do it for a production system, or an application that I intended to provide to someone else. Production support folks will (rightly) have a fit if they are asked to do this sort of thing.
But if you really want to do this, the answers from #bmargulies and #akf are helpful.
On MacOS (at least on SnowLeopard), the java command unconditionally adds an extra jar to the classpath:
/System/Library/Frameworks/JavaVM.framework/Versions/A/Resources/.compatibility/14compatibility.jar.
This jar contains a version of Apache Xerces+Xalan, unrenamed. This can cause chaotic results for applications that are trying explicitly to use some other versions of these libraries, particularly in webapps in servlet containers.
I tried to avoid this by using OpenJDK from MacPorts, but the MacPorts build failed for it.
Has anyone worked out some other recipe, other than the obvious violence of deleting that JAR file? It's recommended on one blog, but I fear that some Apple component or another will fail without it.
I haven't had any problems after renaming 14compatibility.jar. Perhaps you could try doing that. If anything breaks horribly, you could move it back in its original place.
I believe the ultimate trump card here is -Xbootclasspath/p:foo.jar . This lets you prepend a .jar to the bootstrap classloader. This should make it take precedence over anything I can imagine. For example you can replace java.lang.String this way.
I've a chunk of code that works fine in isolation, but used a dependency in a clients project fails. A call to
Document doc = impl.createDocument(null,null,null);
fails (looks like the problem at http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4913257). The instance of 'impl' in my unit tests is an instance of com.sun.org.apache.xerces.internal.dom.DOMImplementationImpl. In my clients code, its an instance of org.apache.xerces.dom.DOMImplementationImpl.
How do I trace back this dependency? I've tried manually going through all classes and jar files in the classpath, but cannot find the provider of org.apache.xerces.dom.DOMImplementation. Is is possible to observe when classes are loaded (and why)? How is the particular DOM implementation selected? Can I for now force the jvm to use a particular implementation?
The implementation in the "com.sun.org.apache..." package is the Xerces that's packaged as part of the JRE. The one starting "org.apache..." is the standalone distribution from Apache. They can be run together in the same application, but it can get quite confusing.
Your client's project would appear to contain a copy of the standalone apache distribution (probably xercesImpl.jar). Ask them to rem,ove it and see if it starts using the built-in JRE code.
Most likely you do not have xercesImpl.jar in the client project's classpath
I think you could deal with endorsed standards stuff.
I've trying to use Eclipse JDT AST parsing classes. After including the initial JAR, and sorting out a couple more dependencies, it is with 7+ JARs and I still having NoClassDefFoundError exceptions. This situation arises whenever I'm trying to test libraries with little or no documentation. Trial and error seems a very dumb (and annoying) approach to solve this problem.
Is there a way to automatically sort this out using Eclipse?
Update: Later I found that adding all the JARs you have, and using Ctrl-T (to view/locate types), lets you manually locate the JAR. That was the solution that Google provided so far. Is there a better way?
If you refer to this SO question Finding unused jars used in an eclipse project, you also have:
ClassPathHelper, which can quickly focus on unresolved classes:
It automatically identifies orphan jars, blocked (obscured) classes, and much more.
The only limit is dependencies that are not defined in classes, e.g. in dependency injection framework configuration files.
I have found setting up a workspace exclusively for browsing the eclipse source code incredibly useful. In this manner, you can use PDE tools like the Plug-in Spy, bundle dependency analysis, browsing the documentation, etc much like you would your own plugin projects. I found this article at Vogella a very useful guide.
If you know which bundle your desired class is you can generate the transitive closure of dependencies by creating a new OSGi launch configuration, with just the single bundle selected. By hitting the Add Required button, you can see all bundles necessary to use the one you're interested in.
Edit:
From your question it wasn't clear as to the environment you want to run the compiler in. If you're interested in an embeddable Java compiler to be run outside of an OSGi environment, may I suggest Janino.
You could use a dependency analyzer like:
JarAnalyzer
This will parse a directory full of Jars and give you an XML output dependency map, for which there are several tools for displaying in either graphical or text form.