How to include two different versions of the same dependency? - java

I am working on a customization for an ERP system in Java. In my customization I want to use Apache POI 3.10.1. Therefore I have integrated the jars poi-3.10.1-20140818.jar and poi-ooxml-3.10.1-20140818.jar.
However, these jars contains several classes that are already included in the core code of the ERP System, but have differences.
If the core ERP classes override the POI classes, the customization throws a Runtime exception. Possibly the same will happens with a core functionality if the POI classes override the core classes.
What is a best practice for dealing with a problem like this?
My customization is a relatively isolated functionality.

There are two approaches to solving this problem:
You can isolate the library from the ClassLoader that loads the other version of POI. for now, I assume that the ERP system is on the class path such that you need to isolate the library from the system class loader. You can do so by creating a new instance of an URLClassLoader which you then point to the jar files containing the newer version of POI. Make sure to also add all transient dependencies such as for example commons-codec to avoid class loading issues. Also, note that transient dependencies can have transient dependencies by themselves.
In order to hide the class path from a class loader, you would set the bootstrap class loader as a direct parent which is represented by null:
new URLClassLoader(new URL[]{ new URL("poi-3.10.1-20140818.jar"), ... }, null);
With this class loader, you can query for the newer version POI classes by something like
Class.forName("org.apache.poi.hssf.usermodel.HSSFWorkbook", true, urlClassLoader);
for retreiving the new version of the HSSFWorkbook. Note however that any direct reference to HSSFWorkbook by a literal would be resolved by the class loader of the executing class which would of course link the old, incompatible version of a class. Thus, you need to use reflection for all your code. Alternatively, you add a class to the URLCLassLoader which contains all your logic and only invoke this class via reflection. This is a cleaner approach, in general. For example, you could add a class that implements a bootstrap class such as Callable which you then can use from any different context as for example:
Callable<File> sub = (Callable<File>) Class.forName("pkg.Subroutine",
true,
urlClassLoader);
File convertedFile = sub.call();
Alternatively, you can repackage the second POI dependency into another name space. After doing this, the classes are not conflicting anymore as their names are not longer equal. This is probably a cleaner approach as you can then use both libraries from the same class loader and you avoid reflection.
For repackaging a dependency into another name space, there are tools like the Maven Shade plugin that can help you with this task. Alternatives are jarjar for ant or the Shadow plugin for Gradle.

If you are using Servlet 3.0 API and you can change some configuration, "web fragments" can be utilized for such kind of situation. Following is the explanation: http://www.oracle.com/technetwork/articles/javaee/javaee6overview-part2-136353.html#webfrags

Related

PsiClass to java.lang.Class

I'm developing plugin for IntelliJ IDEA. How can plugin get the name and version of libraries that are imported to the project that is being checked by plugin? I have PsiClass of the project, but cannot convert it to java.lang.Class. Maybe there's the way to get ClassLoader from PsiElement?
super.visitImportStatement(psiImport);
Class importedClass = Class.forName(psiImport.getQualifiedName(), true, psiImport.getClass().getClassLoader());
PsiImport.getClass().GetClassLoader() - returns ClassLoader of class PsiImportStatementImpl instead of ClassLoader of class that I've imported.
IntelliJ does mostly static analysis on your code. In fact, the IDE and the projects you run/debug have completely different classpaths. When you open a project, your dependencies are not added to the IDE classpath. Instead, the IDE will index the JARs, meaning it will automatically discover all the declarations (classes, methods, interfaces etc) and save them for later in a cache.
When you write code in your editor, the static analysis tool will leverage the contents of this index to validate your code and show errors when you're trying to use unknown definitions for example.
On the other hand, when you run a Main class from your project, it will spawn a new java process that has its own classpath. This classpath will likely contain every dependency declared in your module.
Knowing this, you should now understand why you can't "transform" a PsiClass to a corresponding Class.
Back to your original question:
How can plugin get the name and version of libraries that are imported to the project that is being checked by plugin?
You don't need to access Class objects for this. Instead, you can use IntelliJ SDK libraries. Here's an example:
Module mod = ModuleUtil.findModuleForFile(virtualFile,myProject);
ModuleRootManager.getInstance(mod).orderEntries().forEachLibrary(library -> {
// do your thing here with `library`
return true;
});

Java package versions - getting it right

I would like to get deeper understanding on how Java deals with different versions of Classes/Packages/etc., but couldn't find any resources or at least the best way to google for it. The problem is as follows.
Imagine we have some external package com.external.package that contains a definition of SomeInterface.
Now I write a java class MyClass that implements SomeInterface, and using com.external.package v1.0.0. Next I package a (lean) jar containing MyClass.
Now I plug this jar in another program that is looking for implementations of SomeInterface, but in it's dependencies, it is using com.external.package v2.0.0.
Is the reason I get Failed to find any class that implements SomeInterface that versions of SomeInterface don't match in the program and in the jar that contains a class extending it?
Basically the question I would like to find an answer for is what info do jars store regarding external dependencies? Does it store the exact versions of them and if they don't match at the runtime it complains? But why does it even allow running the program with references to same dependency, but different versions?
Is the reason I get Failed to find any class that implements SomeInterface that versions of SomeInterface don't match in the program and in the jar that contains a class extending it?
There is no "versioning" happening here. Simply, the error states no such class exists on the classpath. For example, you didn't put a -cp in your java command to add that extra JAR/class file.
Other reasons this could happen is that an API marks a class as deprecated in v1, then decides to remove it from v2. In which case, you best try to compile and test your code against the proper library versions before you package your own code. If you made an uber JAR, the classes should get shaded, and you probably wouldn't have missing classes.
Maven projects do have the concept of transitive, versioned dependencies, but you've not said anything about that
Seeing that the original question has found an answer already, it seems somewhat relevant to mention that Java Packages and JARs could be used for specifying package version information as discussed in the following documentation:
https://docs.oracle.com/javase/8/docs/technotes/guides/versioning/spec/versioning2.html#wp89936
Also, the Oracle Java Tutorials discuss them and further concepts around deployment of programs as JAR Files as documented here:
https://docs.oracle.com/javase/tutorial/deployment/jar/index.html

Can Gradle help solve jar hell in any way?

Java 8 here.
Say there is an old version of the widget libray, with Maven coordinates widgetmakers:widget:1.0.4, that has a class defined in it like so:
public class Widget {
private String meow;
// constructor, getters, setters, etc.
}
Years pass. The maintainers of this widget library decide that a Widget should never meow, rather, that it should in fact bark. And so a new release is made, with Maven coordinates widgetmakers:widget:2.0.0 and with Widget looking like:
public class Widget {
private Bark bark;
// constructor, getters, setters, etc.
}
So now I go to build my app, myapp. And, wanting to use the latest stable versions of all my dependencies, I declare my dependencies like so (inside of build.gradle):
dependencies {
compile (
,'org.slf4j:slf4j-api:1.7.20'
,'org.slf4j:slf4j-simple:1.7.20'
,'bupo:fizzbuzz:3.7.14'
,'commons-cli:commons-cli:1.2'
,'widgetmakers:widget:2.0.0'
)
}
Now let's say that this (fictional) fizzbuzz library has always depended on a 1.x version of the widget library, where Widget would meow.
So now, I'm specifying 2 versions of widget on my compile classpath:
widgetmakers:widget:1.0.4 which is pulled in by the fizzbuzz library, as a dependency of it; and
widgetmakers:widget:2.0.0 which I am referencing directly
So obviously, depending on which version of Widget gets classloaded first, we will either have a Widget#meow or a Widget#bark.
Does Gradle provide any facilities for helping me out here? Is there any way to pull in multiple versions of the same class, and configure fizzbuzz classes to use the old version of Widget, and my classes to use the new version? If not, the only solutions I can think of are:
I might be able to accomplish some kind of shading- and/or fatjar-based soltuion, where perhaps I pull in all my dependencies as packages under myapp/bin and then give them different version-prefixes. Admittedly I don't see a clear solution here, but am sure something is feasible (yet totally hacky/nasty). Or...
Carefully inspect my entire dependency graph and just make sure that all of my transitive dependencies don't conflict with each other. In this case for me, this means either submitting a pull-request to the fizzbuzz maintainers to upgrade it to the latest widget version, or, sadly, downgrading myapp to use the older widget version.
But Gradle (so far) has been magic for me. So I ask: is there any Gradle magic that can avail me here?
Don't know the specifics of Gradle, as I'm a Maven person, but this is more generic anyway. You basically have two options (and both are hacky):
ClassLoader magic. Somehow, you need to convince your build system to load two versions of the library (good luck with that), then at runtime, load the classes that use the old version with a ClassLoader that has the old version. I have done this, but it's a pain. (Tools like OSGI may take away some of this pain)
Package shading. Repackage the library A that uses the old version of library B, so that B is actually inside A, but with a B-specific package prefix. This is common practice, e.g. Spring ships its own version of asm. On the Maven side, the maven-shade-plugin does this, there probably is a Gradle equivalent. Or you can use ProGuard, the 800 pound gorilla of Jar manipulation.
Gradle will only set up the classpath with your dependencies, it doesn't provide its own runtime to encapsulate dependencies and its transitive dependencies. The version active at runtime will be the one according to the classloading rules, which I believe is the first jar in the classpath order to contain the class. OSGI provides runtime that can deal with situations like this and so will the upcoming module system.
EDIT: Bjorn is right in that it will try to resolve conflicts in different versions; it'll compile the classpath based on its strategies, so the order you put your dependencies in the file doesn't matter. However you still only get one class per classname, it won't resolve OP's issue
If you have different versions of a library with otherwise equal coordinates, Gradles conflict resolution mechanism comes into play.
The default resolution strategy is to use the newest requested version of the library. You will not get multiple versions of the same library in your dependendcy graph.
If you really need different versions of the same library at runtime you would have to either do some ClassLoader magic which definitely is possible or do some shading for one of the libraries or both.
Regarding conflict resolution, Gradle has built-in the newest strategy that is default and a fail strategy that fails if different versions are in the dependency graph and you have to explicitly resolve version conflicts in your build files.
Worse case is when the same class appears in multiple jars. This is more insidious - look at the metrics jars from Codahale and Dropwizard with incompatible versions of the same class in the two jars.
The gradle classpath-hell plugin can detect this horror.

Same class different version, dependencies, NoSuchMethod

I am working on a web project that has 2 different dependencies being pulled into war file of the same class
(different versions, different package)
One is :
com.google.common.collect, and the other is Guava API package. When I run this service on websphere application server, it throws NoSuchMethodFound Exception at ImmutableList.copyOf. It clearly is loading the earlier class instead of the class from Guava which has the required functions.
I cannot change any dependency, how ever is it possible for me to override a particular dependency by other using maven?
How should I solve this problem?
If you have control over the WebSphere installation, you can also try this:
locate the jre lib directory of your application server (/WebSphere/AppServer/java/jre/lib)
create a directory 'endorsed' put your required jars into this directory( Guava API).
The jars in this directory will be loaded first and override what you have in you war file.
This is not recommended but you can use it as patch to override the conflicting classes.
The first matched class found on the classpath is used. Therefore if you can specify the classpath in different ways to try and influence what class is picked up. (i.e. specify the class that you want loaded first in the classpath). This is not a good practice because the Java specification does not guarntee to use the classpath order.
A better solution would be to manage the classloading yourself in the code. This can be done by
`ClassLoader myClassLoader = new MyClassLoader(libPath);
Object1 obj1 = myClassLoader .loadClass("com.google.common.collect", true);'
Now if a classloader attempts to load classes from a library, the dependent classes would be loaded by the same classloader that does not have access to the other libraries and dependencies.
Note: That if you use this and want to move to OSGi in the future you will incure some pain having to remove this code. Therefore try to limit it's use or switch to OSGi early!

How to create a JAR with only the class files needed by a given class

I have a directory full of hundreds of class files that have been constructed by a previous compilation. Let's say I have a class which only depends upon a small subset of those generated class files. Is it possible to create a JAR which only has the dependencies for the given class?
EDIT: Please note that I am not speaking of the library level dependencies (i.e. JARs). When I refer to dependencies above, I am referring to the sort of dependency that results from class A calling class B. Perhaps an example would be good. Imagine I have the following classes in my project.
public class A {
B bField;
}
public class B {
C cField;
}
public class C {
B bField;
}
Now imagine I want to build a JAR with class B, then the JAR would also need to include the class file for C because the one depends upon the other. If I wanted to build a JAR from class A, then all three classes would be included. Is there a way to examine this dependency chain and build a JAR with the result?
Several products can do this, including ProGuard.
The danger is that without exhaustive run-time analysis, or a good understanding of your code and any frameworks it uses, some classes may be missed if they've instantiated via reflection. Plugin systems, dependency injection, scripting, and so on can all interfere with the accuracy of static analysis.
Yes it is possible. you can create a file with the list of classes that you do want to include and pass that file into the jar command. The details are documented here.

Categories

Resources