Same class different version, dependencies, NoSuchMethod - java

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!

Related

How class loader works where jvm has jars having same names but different versions, which one would be loaded at runtime or both will?

There are two jars which have the same name which is being used in the application but for different purposes.
The name of the jar for example is "A1.jar" having different versions and used in if else condition.
if this:
then A1-10.2.3jar.create()
else:
then A1-8.18.0jar.create()
which in turn have different implementations of create method.
The question is when the application is loaded which jar will be instantiated during class loading time? Or depending upon condition the corresponding jar will be loaded? How can we ensure the correct jar is picked up each time.
The question is further transported to application server scenario where both libs are in WEB-INF/lib folder on jboss/wildfly. How would classloader behave there?
This happened in one of the cases for wildfly where had same name of jars but it was picking up the wrong one and creating an issue. But the same case was running fine on another environment..is there any order/precedence for this?
Update:
The classes are different when it is being called:
Say A2 and A3 class which then calls the A1 jar which are coming as a dependency from class A2 and A3 with same names but different versions.
So, this will be the case:
if this:
then A2.create()-> calls A1-10.2.3jar.respone()
else:
then A3.create()->calls A1-8.18.0jar.respone()
If this is the case can classloader load both the classes or it can be random?
As far as i know, there is no specification for jee application server which version they have to use in this scenario. Therefore it's kind of (deterministic) random, which jar is used.
This really should be avoided, for example with an exclude for the deployment in a maven file or something similar for gradle.

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;
});

Can I force the order of dependencies in my classpath with Gradle?

A project runs on Google App Engine. The project has dependency that uses a class that can't be invoked on App Engine due to security constraints (it's not on the whitelist). My (very hacky) solution was to just copy a modified version of that class into my project (matching the original Class's name and package) that doesn't need the restricted class. This works on both dev and live, I assume because my source appears in the classpath before my external dependencies.
To make it a bit cleaner, I decided to put my modified version of that class into it's own project that can be packaged up in a jar and published for anyone else to use should they face this problem.
Here's my build.gradle:
// my jar that has 'fixed' version of Class.
compile files('path/to/my-hack-0.0.1.jar')
// dependency that includes class that won't run on appengine
compile 'org.elasticsearch:elasticsearch:1.4.4'
On my local dev server, this works fine, the code finds my hacked version of the class first at runtime. On live, for some unknown reason, the version in the elasticsearch dependency is loaded first.
I know having two versions of the same class in the classpath isn't ideal but I was hoping I could reliably force my version to be at the start of the classpath. Any ideas? Alternatively, is there a better way to solve this problem?
Not really sure if this is what people visiting this question were looking for, but this was what my problem and a solution that I reached at.
Jar A: contains class XYZ
Jar B: also contains class XYZ
My Project needs Jar B on the classpath before Jar A to be able to get compiled.
Problem is Gradle sorts the dependencies based on alphabetical order post resolving them which meant Jar B will be coming after Jar A in the generated classpath leading to error while compiling.
Solution:
Declare a custom configuration and patch the compileClasspath. This is how the relevant portion of build.gradle might look like.
configurations {
priority
sourceSets.main.compileClasspath = configurations.priority + sourceSets.main.compileClasspath
}
dependencies {
priority 'org.blah:JarB:2.3'
compile 'org.blah:JarA:2.4'
...
}
It's the app engine classloader I should have been investigating, not gradle...
App Engine allows you to customise the class loader JAR ordering with a little bit of xml in your appengine-web.xml. In my case:
<class-loader-config>
<priority-specifier filename="my-hack-0.0.1.jar"/>
</class-loader-config>
This places my-hack-0.0.1.jar as the first JAR file to be searched for classes, barring those in the directory war/WEB-INF/classes/.
...Thanks to a nudge in the right direction from #Danilo Tommasina :)
UPDATE 2020:
I just hit the same problem again and came across my own question... This time, live appengine was loading a different version of org.json than was being loaded in dev. Very frustrating and no amount of fiddling the build script would fix it. For future searchers, if you're getting this:
java.lang.NoSuchMethodError: org.json.JSONObject.keySet()Ljava/util/Set;
It's because it's loading an old org.json dependency from god-knows-where. I fixed it by adding this to my appengine-web.xml:
<class-loader-config>
<priority-specifier filename="json-20180130.jar"/>
</class-loader-config>
You'll also need a matching dependency in build.gradle if you don't already have one:
compile 'org.json:json:20180130'
According to gradle dependencies documentation, the order of dependencies defines the order in the classpath. So, we can simply put the libraries in the correct order in "dependencies".
But beware! here are two rules with higher priorities:
For a dynamic version, a 'higher' static version is preferred over a 'lower' version.
Modules declared by a module descriptor file (Ivy or POM file) are preferred over modules that have an artifact file only.

How to include two different versions of the same dependency?

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

Jar not using contained classes or namespace collision

I'm using netbeans to generate a web service client from a WSDL doc.
The client works fine until we put it in our production environment. The jars were generated against javax.ws.xxxxx classes from jaxws-api.jar and jaxb-api.jar, which we placed in out production classpath.
In our production environment we have other code that depends on xfire libraries. When we attempt to instantiate SubmissionAPI(), we are getting a NoClassDefFound Exception. I'm pasting the stack trace below.
UBLSoapTest class appears to call the correct 'Service' class, but the 'Service' class
is calling org.codehaus.xfire.jaxws.Provider.
I've checked the source for javax.xml.ws.Service, and it has 'import javax.xml.ws.spi.Provider;'
We've tried assembling the jar with the javax.xxxx classes inside and still get the same result. I suspect we need to set a classpath in the manifest file, but I'm not sure what needs to be there.
FAIL: Exception: java.lang.NoClassDefFoundError: Could not initialize class
org.codehaus.xfire.jaxws.JAXWSHelper
org.codehaus.xfire.jaxws.ServiceDelegate.<init>(ServiceDelegate.java:33)
org.codehaus.xfire.jaxws.ServiceDelegate.<init>(ServiceDelegate.java:53)
org.codehaus.xfire.jaxws.Provider.createServiceDelegate(Provider.java:32)
javax.xml.ws.Service.<init>(Service.java:56)
org.ubl.soap.test.SubmissionAPI.<init>(SubmissionAPI.java:44)
I'm a bit at a loss of even where to look from here.
We've tried setting classpath in the manifest, with little success.
The basic jar structures we've tries are:
/org/xxxx
/META-INF/xxx
and
/org/xxx
/META-INF/xxx
/javax/xxx
and
/org/xxx
/META-INF/xxx
/jaxws-api.jar
/jaxb.jar
It seems that you are missing the impl jar for JAX-WS. If you are relying on Codehaus to provide the impl, you might want to make sure their impl works. According to their site, it's a newly supported component.
The alternative is to provide your own impl jar and put it in the class path so JAX-WS interfaces are found there. But usually the app server where you deploy your web service app should include the impl in its class path.

Categories

Resources