Selectively using third-party implementation for deprecated JavaEE modules - java

I am currently porting an open source library to be JDK9+ compliant, and it depends on some of the Java EE Modules that have been deprecated in Java 9 and removed in Java 11: specifically, JAXB, JAX-WS and javax.annotation.
I added explicit dependencies to the third party implementations as suggested here:
<dependency>
<groupId>com.sun.xml.ws</groupId>
<artifactId>jaxws-ri</artifactId>
<version>2.3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-ri</artifactId>
<version>2.3.0.1</version>
</dependency>
<dependency>
<groupId>com.sun.activation</groupId>
<artifactId>javax.activation</artifactId>
<version>1.2.0</version>
</dependency>
However, I'd like my library to use them only if necessary (i.e., on JDK9+) and keep using the endorsed implementations on JDK8.
I can do so by adding the dependencies in a Maven profile to be activated only on JDK 9 and above, but what if I wanted to publish the jar file for my library on Maven Central? Should I publish two different jars, one with the Java EE third party implementations included, for JDK9+ and one without for JDK8?
Is there a way to produce a jar file that will use the third party implementations on JDK9+ and the endorsed ones on JDK8?
I have looked into multi-release jars, but looks like they are intended for jdk version-dependent implementations among project classes, not among dependencies.
Also, in case it's not possible to use the endorsed implementations on JDK 8, is there a way to reliably test that using the third party implementations does not introduce any regressions?

Is there a way to produce a jar file that will use the third party
implementations on JDK9+ and the endorsed ones on JDK8?
Unfortunately, no. When distributing a library via jar file, you cannot control how other jars and libraries will be listed in the classpath. This makes class loading non-deterministic for you. What that means for your situation is that if the aforementioned libraries are included in the classpath in a JDK8 environment, there is no way to determine or control which version of the classes get loaded.
Also, in case it's not possible to use the endorsed implementations on
JDK 8, is there a way to reliably test that using the third party
implementations does not introduce any regressions?
As the author, it would be up to you to execute tests across the different runtime environments to check for regressions.
Should I publish two different jars, one with the Java EE third party
implementations included, for JDK9+ and one without for JDK8?
This is a perfectly reasonable solution which others have used before as well. Take for example the sqlserver jdbc jars, which have different versions based on jre: https://learn.microsoft.com/en-us/sql/connect/jdbc/using-the-jdbc-driver?view=sql-server-2017
For the case described here, the JDK9+ version of the jar could declare the additional dependencies you mentioned in the question, whereas the JDK8 version would not.
One other option would be to have a single jar and to declare the dependecies you mentioned as provided. This would signal to the consumer that the runtime environment must include the classes in the declared dependencies. Some documentation would be called for to direct the consumer of the library as to when to they must explicitly add the jars declared as provided to their classpath.
IMHO, the clearest solution is two jars with a reference to the JRE version in the jar name and differing dependencies. It requires very little documentation (which most dont look at anyway). And it allows you to make changes in your library more freely.

Related

Java 9 modules and uber-jar

When developing Java libraries we're currently using the Apache Maven Shade Plugin to hide internal dependencies on other libraries (jars) by renaming their package names.
Is it possible to hide these internal library-dependencies by using the Java 9 module system and not exporting the name of the internally used libraries?
I.e:
Both module A and B include, but does not export, class org.codehaus.jackson.map.ObjectMapper (included using e.g. Maven Shade plugin) with different versions for the class
Module A uses module B
Will each module still use its implementation org.codehaus.jackson.map.ObjectMapper?
I believe it should by so, but I have found no documentation explicitely confirming this, nor any texts / examples recommending this approach for this quite usual versioning issue.
This issue is described as http://openjdk.java.net/projects/jigsaw/spec/issues/#MultiModuleExecutableJARs and there's no support for it yet. In case all dependencies are modules, it would make sense to use jlink to solve this. But as long as there is at least one non-module, there's no solution available yet. This is something that needs to be solved within the JDK/JRE.
It is still a valid case, so I would suggest to ask this question the at jigsaw-dev mailinglist and refer to #MultiModuleExecutableJARs

Provided dependency and JBOSS EAP 7

I understand that PROVIDED dependencies are "provided" by container and application don't need generate this JAR.
1) So, I'm using JBOSS EAP 7.0.0.GA and this have the following jar in this module folder: hibernate-core-5.0.9.Final-redhat-1.jar.
In my project i'm using the following dependency:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.0.9.Final-redhat-1</version>
</dependency>
It works fine, without errors. But i understand that i should use "PROVIDED" scope because this jar is provided by container. Why it works ?
2) I have another example. In Jboss Eap 7.0.0.GA i have the following jar: jboss-servlet-api_3.1_spec-1.0.0.Final-redhat-1.jar. But in my project i have the following:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
<optional>true</optional>
</dependency>
It works fine too, but i don't understand why. To me the correct dependency should be servlet-api_3.1_spec-1.0.0.Final-redhat-1 with provided. Why it works too ?
At build time, Maven will resolve the dependencies and make the relevant packages available to the compiler. Your appserver may provide, for example, JARs that contain the classes and interfaces in javax.servlet, but these classes won't necessarily be useful to the compiler, because it doesn't know where they are. By providing the dependencies to Maven, you're having Maven find its own implementations of these dependencies, just for use at compile time.
At run time, if you've marked the dependencies as provided, then your application will use the versions provided by the appserver, rather than the versions known to Maven. This is potentially a bad thing, but it often works because the compiler really needs to know only the method signatures, not their implementations. The method signatures of classes that are controlled by specifications, such as those in javax.servlet, change only infrequently, so the mismatch between the compile-time JARs and run-time JARs may go unnoticed. Unlike OSGi-compliant JARs, JARs built for JEE do not contain meta-data that specifies particular compatible dependency versions -- JEE classloaders will use what they find, for better or worse.
You can be caught out, however -- particularly if the mismatch is substantial. Problems may be very obvious at runtime, such as exceptions related to missing classes or methods, but they can be subtle.
It's therefore often best, where practicable, to use the same compile-time versions of dependencies as the versions that will be available at runtime. For EAP, I recall that Red Hat distributes a Maven bill-of-materials (BOM) file that specifies all the versions of all the EAP JARs for specific EAP releases.

Can Maven generate the module declaration?

The module declaration defines, among other things, a module's dependencies. If I use Maven as a build tool, this is redundant because the pom.xml already contains these (and more) information. Based on that, couldn't Maven generate the module-info.java for me?
One might expect that most of the dependencies are indeed required modules as well. However, requirements can also point to modules of the JDK/JRE, which are not specified in the pom.xml. So yes, if you only look at the dependencies, probably most of them could be transformed to a required module reference.
But a module-descriptor contains much more information, which are all based on decisions to be made by the developer.
I've written an article about it which describes in detail why it is not possible to fully generate this file.
As far as I know, bnd-maven-plugin can generate module-info.class based on the configured dependencies. If you are working with maven-bundle-plugin, you need to specify the version of bndlib manually, for the latest version of maven-bundle-plugin(5.1.3) is still using the 5.x version of bndlib, and bndlib requires 6.x to support jpms.
Document: https://bnd.bndtools.org/releases/6.1.0/chapters/330-jpms.html

using two versions (with code conflicts) of a 3rd party jar in one maven project

Our maven project includes one 3rd third jar, which has two different versions.
A-1.0.jar (with function A() removed in version 2)
A-2.0.jar (with function B() added (not in version 1))
How to organize my codes to support 1.0's api and 2.0's api at the same time ?
A possible way of doing this is to have no dependency on library A (not version 1.0, nor version 2.0) in your project.
You need to create three other build (maven for example) projects / artifacts, which will be wrappers for the different library versions.
You'll have :
AbstractWrapper project artifact
Wrapper of version 1 artifact
Wrapper of version 2 artifact
In the AbstractWrapper, you'll have an interface / abstract class, that reproduce the api from the library A you want to use. This is the only [of the 3] artifacts that will be declared in the dependencies of your main project. This abstract wrapper do not depend on your project, nor on lib A.
Then each wrapper will depend on AbstractWrapper (to get the interface definition), and on the A library, either version 1 or version 2.
The tricky point now is to get the wrapper implementation (that will be available at runtime) to register in your main project. It depends a lot on the packaging and the runtime environnements (standalone jar, osgi bundle, servlet spec 2.5-, servlet spec 3.0+, springframework...), i let you do some research on plugin registration for your specific environnement.
The ugliest thing you could come up with is a Class.forName() trying both implementation you did, and using the first it finds (casting the class you get to the abstract wrapper interface).
Finally for the clients using the version 1.0 of the lib A, you tell them to depends on your main project and your wrapper of lib version 1, and the others on your main project and lib version 2.
You can't. But you can exclude one or the other version with the tag like this:
<dependency>
<groupId>sample.ProjectB</groupId>
<artifactId>Project-B</artifactId>
<version>1.0-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>sample.ProjectD</groupId> <!-- Exclude Project-D from Project-B -->
<artifactId>Project-D</artifactId>
</exclusion>
</exclusions>
</dependency>
See the documentation here: https://maven.apache.org/guides/introduction/introduction-to-optional-and-excludes-dependencies.html

Dealing with "Xerces hell" in Java/Maven?

In my office, the mere mention of the word Xerces is enough to incite murderous rage from developers. A cursory glance at the other Xerces questions on SO seem to indicate that almost all Maven users are "touched" by this problem at some point. Unfortunately, understanding the problem requires a bit of knowledge about the history of Xerces...
History
Xerces is the most widely used XML parser in the Java ecosystem. Almost every library or framework written in Java uses Xerces in some capacity (transitively, if not directly).
The Xerces jars included in the official binaries are, to this day, not versioned. For example, the Xerces 2.11.0 implementation jar is named xercesImpl.jar and not xercesImpl-2.11.0.jar.
The Xerces team does not use Maven, which means they do not
upload an official release to Maven Central.
Xerces used to be released as a single jar (xerces.jar), but was split into two jars, one containing the API (xml-apis.jar) and one containing the implementations of those APIs (xercesImpl.jar). Many older Maven POMs still declare a dependency on xerces.jar. At some point in the past, Xerces was also released as xmlParserAPIs.jar, which some older POMs also depend on.
The versions assigned to the xml-apis and xercesImpl jars by those who deploy their jars to Maven repositories are often different. For example, xml-apis might be given version 1.3.03 and xercesImpl might be given version 2.8.0, even though both are from Xerces 2.8.0. This is because people often tag the xml-apis jar with the version of the specifications that it implements. There is a very nice, but incomplete breakdown of this here.
To complicate matters, Xerces is the XML parser used in the reference implementation of the Java API for XML Processing (JAXP), included in the JRE. The implementation classes are repackaged under the com.sun.* namespace, which makes it dangerous to access them directly, as they may not be available in some JREs. However, not all of the Xerces functionality is exposed via the java.* and javax.* APIs; for example, there is no API that exposes Xerces serialization.
Adding to the confusing mess, almost all servlet containers (JBoss, Jetty, Glassfish, Tomcat, etc.), ship with Xerces in one or more of their /lib folders.
Problems
Conflict Resolution
For some -- or perhaps all -- of the reasons above, many
organizations publish and consume custom builds of Xerces in their
POMs. This is not really a problem if you have a small application and are only using Maven Central, but it quickly becomes an issue for enterprise software where Artifactory or Nexus is proxying multiple repositories (JBoss, Hibernate, etc.):
For example, organization A might publish xml-apis as:
<groupId>org.apache.xerces</groupId>
<artifactId>xml-apis</artifactId>
<version>2.9.1</version>
Meanwhile, organization B might publish the same jar as:
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.3.04</version>
Although B's jar is a lower version than A's jar, Maven does not know
that they are the same artifact because they have different
groupIds. Thus, it cannot perform conflict resolution and both
jars will be included as resolved dependencies:
Classloader Hell
As mentioned above, the JRE ships with Xerces in the JAXP RI. While it would be nice to mark all Xerces Maven dependencies as <exclusion>s or as <provided>, the third-party code you depend on may or may not work with the version provided in JAXP of the JDK you're using. In addition, you have the Xerces jars shipped in your servlet container to contend with. This leaves you with a number of choices: Do you delete the servlet version and hope that your container runs on the JAXP version? Is it better to leave the servlet version, and hope that your application frameworks run on the servlet version? If one or two of the unresolved conflicts outlined above manage to slip into your product (easy to happen in a large organization), you quickly find yourself in classloader hell, wondering which version of Xerces the classloader is picking at runtime and whether or not it will pick the same jar in Windows and Linux (probably not).
Solutions?
We've tried marking all Xerces Maven dependencies as <provided> or as an <exclusion>, but this is difficult to enforce (especially with a large team) given that the artifacts have so many aliases (xml-apis, xerces, xercesImpl, xmlParserAPIs, etc.). Additionally, our third party libs/frameworks may not run on the JAXP version or the version provided by a servlet container.
How can we best address this problem with Maven? Do we have to exercise such fine-grained control over our dependencies, and then rely on tiered classloading? Is there some way to globally exclude all Xerces dependencies, and force all of our frameworks/libs to use the JAXP version?
UPDATE: Joshua Spiewak has uploaded a patched version of the Xerces build scripts to XERCESJ-1454 that allows for upload to Maven Central. Vote/watch/contribute to this issue and let's fix this problem once and for all.
There are 2.11.0 JARs (and source JARs!) of Xerces in Maven Central since 20th February 2013! See Xerces in Maven Central. I wonder why they haven't resolved https://issues.apache.org/jira/browse/XERCESJ-1454...
I've used:
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.11.0</version>
</dependency>
and all dependencies have resolved fine - even proper xml-apis-1.4.01!
And what's most important (and what wasn't obvious in the past) - the JAR in Maven Central is the same JAR as in the official Xerces-J-bin.2.11.0.zip distribution.
I couldn't however find xml-schema-1.1-beta version - it can't be a Maven classifier-ed version because of additional dependencies.
Frankly, pretty much everything that we've encountered works just fine w/ the JAXP version, so we always exclude xml-apis and xercesImpl.
You could use the maven enforcer plugin with the banned dependency rule. This would allow you to ban all the aliases that you don't want and allow only the one you do want. These rules will fail the maven build of your project when violated. Furthermore, if this rule applies to all projects in an enterprise you could put the plugin configuration in a corporate parent pom.
see:
http://maven.apache.org/plugins/maven-enforcer-plugin/
http://maven.apache.org/enforcer/enforcer-rules/bannedDependencies.html
I know this doesn't answer the question exactly, but for ppl coming in from google that happen to use Gradle for their dependency management:
I managed to get rid of all xerces/Java8 issues with Gradle like this:
configurations {
all*.exclude group: 'xml-apis'
all*.exclude group: 'xerces'
}
I guess there is one question you need to answer:
Does there exist a xerces*.jar that everything in your application can live with?
If not you are basically screwed and would have to use something like OSGI, which allows you to have different versions of a library loaded at the same time. Be warned that it basically replaces jar version issues with classloader issues ...
If there exists such a version you could make your repository return that version for all kinds of dependencies. It's an ugly hack and would end up with the same xerces implementation in your classpath multiple times but better than having multiple different versions of xerces.
You could exclude every dependency to xerces and add one to the version you want to use.
I wonder if you can write some kind of version resolution strategy as a plugin for maven. This would probably the nicest solution but if at all feasible needs some research and coding.
For the version contained in your runtime environment, you'll have to make sure it either gets removed from the application classpath or the application jars get considered first for classloading before the lib folder of the server get considered.
So to wrap it up: It's a mess and that won't change.
You should debug first, to help identify your level of XML hell. In my opinion, the first step is to add
-Djavax.xml.parsers.SAXParserFactory=com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl
-Djavax.xml.transform.TransformerFactory=com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl
-Djavax.xml.parsers.DocumentBuilderFactory=com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
to the command line. If that works, then start excluding libraries. If not, then add
-Djaxp.debug=1
to the command-line.
There is another option that hasn't been explored here: declaring Xerces dependencies in Maven as optional:
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>...</version>
<optional>true</optional>
</dependency>
Basically what this does is to force all dependents to declare their version of Xerces or their project won't compile. If they want to override this dependency, they are welcome to do so, but then they will own the potential problem.
This creates a strong incentive for downstream projects to:
Make an active decision. Do they go with the same version of Xerces or use something else?
Actually test their parsing (e.g. through unit testing) and classloading as well as not to clutter up their classpath.
Not all developers keep track of newly introduced dependencies (e.g. with mvn dependency:tree). This approach will immediately bring the matter to their attention.
It works quite well at our organization. Before its introduction, we used to live in the same hell the OP is describing.
Every maven project should stop depending on xerces, they probably don't really. XML APIs and an Impl has been part of Java since 1.4. There is no need to depend on xerces or XML APIs, its like saying you depend on Java or Swing. This is implicit.
If I was boss of a maven repo I'd write a script to recursively remove xerces dependencies and write a read me that says this repo requires Java 1.4.
Anything that actually breaks because it references Xerces directly via org.apache imports needs a code fix to bring it up to Java 1.4 level (and has done since 2002) or solution at JVM level via endorsed libs, not in maven.
What would help, except for excluding, is modular dependencies.
With one flat classloading (standalone app), or semi-hierarchical (JBoss AS/EAP 5.x) this was a problem.
But with modular frameworks like OSGi and JBoss Modules, this is not so much pain anymore. The libraries may use whichever library they want, independently.
Of course, it's still most recommendable to stick with just a single implementation and version, but if there's no other way (using extra features from more libs), then modularizing might save you.
A good example of JBoss Modules in action is, naturally, JBoss AS 7 / EAP 6 / WildFly 8, for which it was primarily developed.
Example module definition:
<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.1" name="org.jboss.msc">
<main-class name="org.jboss.msc.Version"/>
<properties>
<property name="my.property" value="foo"/>
</properties>
<resources>
<resource-root path="jboss-msc-1.0.1.GA.jar"/>
</resources>
<dependencies>
<module name="javax.api"/>
<module name="org.jboss.logging"/>
<module name="org.jboss.modules"/>
<!-- Optional deps -->
<module name="javax.inject.api" optional="true"/>
<module name="org.jboss.threads" optional="true"/>
</dependencies>
</module>
In comparison with OSGi, JBoss Modules is simpler and faster. While missing certain features, it's sufficient for most projects which are (mostly) under control of one vendor, and allow stunning fast boot (due to paralelized dependencies resolving).
Note that there's a modularization effort underway for Java 8, but AFAIK that's primarily to modularize the JRE itself, not sure whether it will be applicable to apps.
Apparently xerces:xml-apis:1.4.01 is no longer in maven central, which is however what xerces:xercesImpl:2.11.0 references.
This works for me:
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>2.11.0</version>
<exclusions>
<exclusion>
<groupId>xerces</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
<version>1.4.01</version>
</dependency>
My friend that's very simple, here an example:
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<version>2.7.2</version>
<scope>${my-scope}</scope>
<exclusions>
<exclusion>
<groupId>xml-apis</groupId>
<artifactId>xml-apis</artifactId>
</exclusion>
</dependency>
And if you want to check in the terminal(windows console for this example) that your maven tree has no problems:
mvn dependency:tree -Dverbose | grep --color=always '(.* conflict\|^' | less -r

Categories