Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuring classpath for a mixed use of Java XML Binding (javax.xml.bind.*) plus Jakarta XML Binding (jakarta.xml.bind.*) #246

Closed
doljae opened this issue May 17, 2022 · 45 comments

Comments

@doljae
Copy link

doljae commented May 17, 2022

Description

  • A compilation error occurs because the compiled dependency of jpmml-evaluator is insufficient to use.
  • Used version, 1.6.3
  • JDK 17

LoadingModelEvaluatorBuilder.java

image

Question 1

  • I don't understand this situation. It seems that there is no compiled dependency needed to use a particular dependency.
  • (Though I still don't get it) If this is the intended situation, I'm curious as to why.

Question 2

  • Previously, I was using org.jpmml:pmml-evaluator-extension:1.5.16. With this dependency alone, I was able to use all the functions I wanted.
  • However, pmml-evaluator-extension seems to have been discontinued, and I added 7 dependencies to use jpmml as the latest version 1.6.x as before. And I spent a lot of time trying to catch the error.
dependency("org.jpmml:pmml-evaluator:1.6.3")
dependency("org.jpmml:pmml-agent:1.6.3")
dependency("org.jpmml:pmml-model:1.6.3")
dependency("jakarta.xml.bind:jakarta.xml.bind-api:4.0.0")
dependency("jakarta.activation:jakarta.activation-api:2.1.0")
dependency("javax.xml.bind:jaxb-api:2.3.1")
dependency("com.sun.xml.bind:jaxb-impl:3.0.2")
  • Of course, I may be using jpmml wrong. If so, please comment.
  • I know Java EE related packages have been removed from JDK11, and the current LTS version is 17. In this situation, it is strange that the user has to manually add Java EE dependencies (In my case, 4).
  • In any case, it is very inconvenient to use now. I think an update on this is needed in some form. (update README for informing other necessary dependencies, extension support, etc.) I'm curious about maintainer's opinion.
@vruusmann
Copy link
Member

vruusmann commented May 17, 2022

Question 1

The JPMML upgrade from 1.5.X to 1.6.X (initiated in JPMML-Model library, propagated into JPMML-Evaluator library) was about replacing "old" javax.xml.bind.* classes (aka Java XML Binding) with "new" jakarta.xml.bind.* classes (aka Jakarta XML Binding).

As you can imagine, this is a major classpath change, and you should take a couple of minutes to check the project README for instructions.

These instructions suggest that your Java application should depend on the org.jpmml:pmml-evaluator-metro dependency. The -metro suffix indicates that jakarta.xml.bind.* classes will be provided by the GlassFish Metro JAXB runtime - this is a very good default.

Should work with all Java SE versions 1.8 and up - as demonstrated here using GitHub Actions CI (click on some CI run, and you'll see a list of ten Java versions, all green):
https://github.com/jpmml/jpmml-evaluator/actions/workflows/maven.yml

Previously, I was using org.jpmml:pmml-evaluator-extension:1.5.16

The org.jpmml:pmml-evaluator-extension module was merged into the main org.jpmml:pmml-evaluator module.

It was providing a couple of Java-backed user-defined function classes, which now reside inside the org.jpmml.evaluator.functions.* package.

I added 7 dependencies to use jpmml as the latest version 1.6.x as before

You only need this one dependency - org.jpmml:pmml-evaluator-metro:1.6.3.

A proper build manager (such as Apache Maven) should be able to import all transitive dependencies automatically.

I'd strongly advise against importing any Jakarta XML Binding libraries manually. My module dependency declarations are correct, up-to-date and maximally short, so you won't be able to improve the situation in any way.

In any case, it is very inconvenient to use now.

TLDR: replace org.jpmml:pmml-evaluator(-extension) with org.jpmml:pmml-evaluator-metro, and everything will be OK.

@vruusmann
Copy link
Member

Functional duplicate of #234

@doljae
Copy link
Author

doljae commented May 17, 2022

@vruusmann
Thank you for answer.
However, I have already tried the method you suggested.

This is my submodule's build scripts's dependencies

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("org.springframework.cloud:spring-cloud-starter-openfeign")

    runtimeOnly("mysql:mysql-connector-java")
    annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
    testImplementation("org.springframework.boot:spring-boot-starter-test")

    implementation("org.springdoc:springdoc-openapi-ui")
    implementation("io.reactivex.rxjava3:rxjava")

    implementation("com.h2database:h2")
    testImplementation("com.h2database:h2")

    implementation("org.jpmml:pmml-evaluator-metro:1.6.3")
}

And this is the result searching "metro" to IntelliJ dependency analyer

image

Same result, LoadingModelEvaluatorBuilder.java
image

@doljae
Copy link
Author

doljae commented May 17, 2022

@vruusmann
I've already checked the readme and the issue you linked to.
So I thought that using the metro dependency instead would solve it, but I got the same issue.

@doljae
Copy link
Author

doljae commented May 17, 2022

@vruusmann
For accurate verification, create an empty spring boot project with spring-initializer, add only metro dependency, and check. As a result, an error still occurs.

build.gradle

image

SpringBootApplication.java

image

LoadingModelEvaluatorBuilder.java

image

@vruusmann
Copy link
Member

vruusmann commented May 17, 2022

And this is the result searching "metro" to IntelliJ dependency analyer

TBH, your diagnostic tools - SBT(?) and IntelliJ dependency analyzer - are no authority for me.

The fact of the matter is that Apache Maven is able to correctly traverse the dependency chain, and all Apache Maven-based builds compile and execute successfully. There are no classpath errors anywhere.

For example, jump into the pmml-evaluator-metro directory and print the dependency tree:

$ mvn dependency:tree

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building JPMML evaluator GlassFish Metro runtime 1.6-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ pmml-evaluator-metro ---
[INFO] org.jpmml:pmml-evaluator-metro:jar:1.6-SNAPSHOT
[INFO] +- org.jpmml:pmml-evaluator:jar:1.6-SNAPSHOT:compile
[INFO] |  +- org.jpmml:pmml-model:jar:1.6.3:compile
[INFO] |  |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.1:compile (version selected from constraint [2.9.4,2.13.1])
[INFO] |  |  \- jakarta.xml.bind:jakarta.xml.bind-api:jar:3.0.1:compile
[INFO] |  +- com.google.guava:guava:jar:31.0.1-jre:compile (version selected from constraint [19.0,31.0.1-jre])
[INFO] |  |  \- com.google.guava:failureaccess:jar:1.0.1:compile
[INFO] |  \- org.apache.commons:commons-math3:jar:3.6.1:compile (version selected from constraint [3.1,3.6.1])
[INFO] \- org.jpmml:pmml-model-metro:jar:1.6.3:compile
[INFO]    \- org.glassfish.jaxb:jaxb-runtime:jar:3.0.2:compile
[INFO]       +- com.sun.activation:jakarta.activation:jar:2.0.1:compile
[INFO]       \- org.glassfish.jaxb:jaxb-core:jar:3.0.2:compile
[INFO]          +- org.glassfish.jaxb:txw2:jar:3.0.2:compile
[INFO]          \- com.sun.istack:istack-commons-runtime:jar:4.0.1:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.700 s
[INFO] Finished at: 2022-05-17T14:33:26+03:00
[INFO] Final Memory: 37M/1963M
[INFO] ------------------------------------------------------------------------

The org.jpmml:pmml-evaluator-metro dependency includes the org.jpmml:pmml-model-metro dependency. which in turn imports the complete set of Jakarta XML Bind interface and implementation libraries.

@vruusmann
Copy link
Member

As a result, an error still occurs.

Maybe you need to rebuild your IntelliJ project after you change the dependency?

There could be some dependency caching going on.

@doljae
Copy link
Author

doljae commented May 17, 2022

Maybe you need to rebuild your IntelliJ project after you change the dependency?

There could be some dependency caching going on.

I tried but it didn't work.
Even if I think about it, adding the metro dependency as per your opinion seems to add all the necessary dependencies.

 ./gradlew dependencies

...
\--- org.jpmml:pmml-evaluator-metro:1.6.3
     +--- org.jpmml:pmml-evaluator:1.6.3
     |    +--- org.jpmml:pmml-model:1.6.3
     |    |    +--- com.fasterxml.jackson.core:jackson-annotations:[2.9.4, 2.13.1] -> 2.13.2
     |    |    |    \--- com.fasterxml.jackson:jackson-bom:2.13.2
     |    |    |         \--- com.fasterxml.jackson.core:jackson-annotations:2.13.2 (c)
     |    |    \--- jakarta.xml.bind:jakarta.xml.bind-api:3.0.1 -> 2.3.3
     |    |         \--- jakarta.activation:jakarta.activation-api:1.2.2
     |    +--- com.google.guava:guava:[19.0, 31.0.1-jre] -> 31.0.1-jre
     |    |    \--- com.google.guava:failureaccess:1.0.1
     |    \--- org.apache.commons:commons-math3:[3.1, 3.6.1] -> 3.6.1
     \--- org.jpmml:pmml-model-metro:1.6.3
          +--- org.jpmml:pmml-model:1.6.3 (*)
          \--- org.glassfish.jaxb:jaxb-runtime:3.0.2 -> 2.3.6
               +--- jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 (*)
               +--- org.glassfish.jaxb:txw2:2.3.6
               \--- com.sun.istack:istack-commons-runtime:3.0.12
...

I don't know the reason. (Of course there's possibility there's some bug in IntelliJ)
Can you advise for me to fix this issue?...

@vruusmann
Copy link
Member

Can you advise for me to fix this issue?

Update your toolchain to Apache Maven! :-)

@doljae
Copy link
Author

doljae commented May 17, 2022

@vruusmann

Update your toolchain to Apache Maven! :-)

so you mean I replace my build tool to maven right?(use pom.xml, not build.gradle)

@vruusmann
Copy link
Member

so you mean I replace my build tool to maven right?

That would be a major change (bigger than upgrading JPMML-Evaluator from 1.5.X to 1.6.X), so you need to weight the pros and cons, and make a decision yourself.

All I can say is that all JPMML family libraries/applications use Apache Maven. And I can't remember having any classpath issues, ever.

With Apache Maven you can generate IDE project files automatically. For example, for Eclipse IDE you simply do mvn eclipse:eclipse, and you will have project files ready with guaranteed correct classpath.

I believe Apache Maven has similar helper plugin for IntellJ also. Try googling around, maybe that will help you get the project files correct.

@doljae
Copy link
Author

doljae commented May 18, 2022

@vruusmann

Thank you for your kind support.
I made a maven-based spring boot project based on your advice and tested it by adding only metro dependency.

The same issue occurred as a result of the test, and the project cannot be started.
Of course, according to the CI/CD log you shared, it seems that it builds without any problem, but the problem is still that it cannot be used in the actual project after adding a dependency in maven.

I use OpenJDK17(Zulu) & Spring boot 2.6.7 using Spring-Initializer, just add metro dependency and check there's no compile error.

image

image

Share the repository link.

After cloning this project, please check if it builds & runs normally.

@doljae
Copy link
Author

doljae commented May 18, 2022

@vruusmann
plus, if does not work, please re-open this issue for other users' support or comments 🙏

@vruusmann
Copy link
Member

vruusmann commented May 18, 2022

I made a maven-based spring boot project based on your advice and tested it by adding only metro dependency.

I've cloned your repository, and indeed the build fails because of a missing jakarta.xml.bind.JAXBException class:

$ mvn clean install

[INFO] -------------------------------------------------------------
[ERROR] COMPILATION ERROR : 
[INFO] -------------------------------------------------------------
[ERROR] /path/to/jpmml-maven-test/src/main/java/com/example/tetsets/TetsetsApplication.java:[20,52] cannot access jakarta.xml.bind.JAXBException
  class file for jakarta.xml.bind.JAXBException not found

The problem is that the Spring framework overrides Jakarta XML Binding. See below - I'm expecting to see the org.glassfish.jaxb:jaxb-runtime:jar:3.0.2 dependency (according to my above pmml-evaluator-metro test), but the Spring framework has forcefully replaced it with a org.glassfish.jaxb:jaxb-runtime:jar:2.3.6 dependency:

$ mvn dependency:tree

[INFO] --- maven-dependency-plugin:3.2.0:tree (default-cli) @ tetsets ---
[INFO] com.example:tetsets:jar:0.0.1-SNAPSHOT
...
[INFO] +- org.springframework.boot:spring-boot-starter-test:jar:2.6.7:test
...
[INFO] |  +- jakarta.xml.bind:jakarta.xml.bind-api:jar:2.3.3:compile
[INFO] |  |  \- jakarta.activation:jakarta.activation-api:jar:1.2.2:compile
...
[INFO] \- org.jpmml:pmml-evaluator-metro:jar:1.6.3:compile
[INFO]    +- org.jpmml:pmml-evaluator:jar:1.6.3:compile
[INFO]    |  +- org.jpmml:pmml-model:jar:1.6.3:compile
[INFO]    |  |  \- com.fasterxml.jackson.core:jackson-annotations:jar:2.13.2:compile
[INFO]    |  +- com.google.guava:guava:jar:31.0.1-jre:compile (version selected from constraint [19.0,31.0.1-jre])
[INFO]    |  |  \- com.google.guava:failureaccess:jar:1.0.1:compile
[INFO]    |  \- org.apache.commons:commons-math3:jar:3.6.1:compile (version selected from constraint [3.1,3.6.1])
[INFO]    \- org.jpmml:pmml-model-metro:jar:1.6.3:compile
[INFO]       \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.6:compile
[INFO]          +- org.glassfish.jaxb:txw2:jar:2.3.6:compile
[INFO]          +- com.sun.istack:istack-commons-runtime:jar:3.0.12:compile
[INFO]          \- com.sun.activation:jakarta.activation:jar:1.2.2:runtime

TLDR: my classpath definition has been sabotaged by the Spring framework!

@vruusmann
Copy link
Member

vruusmann commented May 18, 2022

The solution, of course, is to tell the Spring framework to stop meddling with my classpath definition.

You can try two things - maybe they work individually, maybe they work only when applied together:

  1. Include an explicit org.glassfish.jaxb:jaxb-runtime:jar:3.0.2 dependency declaration into your pom.xml. This should override any automatic dependency resolution results.
  2. Update the Spring framework dependency declaration to exclude any org.glassfish.jaxb-groupId dependencies coming in from there:
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter</artifactId>
	<!-- THIS -->
	<exclusions>
		<exclusion>
			<groupId>org.glassfish.jaxb</groupId>
			<artifactId>*</artifactId>
		</exclusion>
	</exclusions>
</dependency>

Of course, the Spring framework itself may refuse to work with the updated org.glassfish.jaxb:jaxb-runtime:jar:3.0.2 dependency. You will find it out during testing.

I'll try to apply my suggestions to your repository to see what works.

@vruusmann vruusmann changed the title Cannot resolve symbol 'JAXBException', support extension dependency Jakarta XML Binding version conflict between JPMML-Evaluator and Spring framework May 18, 2022
@vruusmann
Copy link
Member

I'll try to apply my suggestions to your repository to see what works.

The following dependency declaration seems to work with Spring framework:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<exclusions>
			<exclusion>
				<groupId>jakarta.xml.bind</groupId>
				<artifactId>jakarta.xml.bind-api</artifactId>
			</exclusion>
		</exclusions>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.jpmml</groupId>
		<artifactId>pmml-evaluator-metro</artifactId>
		<version>1.6.3</version>
	</dependency>

	<!-- Override Jakarta XML Bind interface -->
	<dependency>
		<groupId>jakarta.xml.bind</groupId>
		<artifactId>jakarta.xml.bind-api</artifactId>
		<version>3.0.1</version>
	</dependency>

	<!-- Override Jakarta XML Bind implementation -->
	<dependency>
		<groupId>org.glassfish.jaxb</groupId>
		<artifactId>jaxb-runtime</artifactId>
		<version>3.0.2</version>
	</dependency>
</dependencies>

@vruusmann
Copy link
Member

vruusmann commented May 18, 2022

@doljae The lesson for you:

When you encounter a classpath conflict then do the following:

  1. Verify the intended classpath of the org.jpmml:pmml-evaluator-metro dependency by printing out its classpath using mvn dependency:tree.
  2. Verify the actual classpath of the org.jpmml:pmml-evaluator-metro dependency after it has been embedded into an actual application (here: Spring framework).
  3. If configurations 1 and 2 match, you're all good to go!
  4. If configurations 1 and 2 do not match, then you need to start tweaking application dependency declarations until configuration 2 is identical to configuration 1. Available tools - dependency overrides and dependency exlcusions, as exemplified in my comments above.

@doljae
Copy link
Author

doljae commented May 18, 2022

@vruusmann
Thank you for your kind answer 🙂
I understand the root cause of this problem through your comment.

If I use pmml-valuator-metro as you commented, no compilation error will occur.

However, although it is not known exactly, if jakarta.xml.bind-api and jaxb-runtime are separately declared and used, there seems to be a problem somewhere in the code that uses the spring framework.

As a result of making and checking the same build.gradle that you commented on pom.xml, there is no problem with using jpmml in an empty project, but it seems that there is a problem with the code using another spring code. (I doubt the something of Spring Test.)

In conclusion, I confirmed that to use jpmml version 1.6.3 as my project was built the same as before, I had to have the following dependency combination.

dependency("org.jpmml:pmml-evaluator:1.6.3") // or using -metro
dependency("jakarta.xml.bind:jakarta.xml.bind-api:3.0.1")
dependency("org.glassfish.jaxb:jaxb-runtime:3.0.2")
dependency("jakarta.activation:jakarta.activation-api:2.1.0")
dependency("javax.xml.bind:jaxb-api:2.3.1")

I think the Spring team already knows this part and expects that it is deliberately eroding the current dependency structure. Nevertheless, I am going to ask the spring team about this issue.

Separately, under the current situation, it is difficult to use jpml 1.6.x with spring projects. So I am considering downgrading to 1.5 version and using the extension dependency that I used before. For the purpose of this dependency, I think it would be okay not to use the latest version.

Anyway, thanks to your comment, I learned a lot about dependency structure. Thank you again 🙇

@vruusmann
Copy link
Member

there seems to be a problem somewhere in the code that uses the spring framework.

Your spring application compiles without errors, but when you actually run it, it throws some classpath conflict error?

In principle, it is possible to configure the build so that JPMML-Evaluator and Spring have their own Jakarta XML Binding library versions (all within the same uber-JAR file). This trick is called "package relocation", and it is rather easy to implement using Apache Maven:
https://maven.apache.org/plugins/maven-shade-plugin/examples/class-relocation.html

I don't have time to provide an example today, but maybe later this week.

@doljae
Copy link
Author

doljae commented May 18, 2022

@vruusmann
I don't know how many times I say thank you.

Anyway, I briefly looked up the concept of package relocation you mentioned, and it seems that the only way to utilize this is to currently enable me to use latest spring boot and jpmml 1.6.x. (I'm trying to see your exact example and apply a gradle script that does the same thing.)

I found the PR below in the spring-boot project, that is, the project responsible for the part where spring automatically manages sub-dependencies.

The milestone for this PR is 2.7.0-M2. In other words, with a very high probability, Glassfish 2.3.6 will be used for 2.7 GA.
Currently, the latest Spring boot version is 2.6.7. It is expected to use 3.x version of Glassfish in the Spring project someday, but it seems like a very distant future.

As far as I know, there doesn't seem to be any way to use the latest version of spring boot with jpmml other than the package relocation you mentioned at the moment.

@vruusmann
Copy link
Member

As far as I know, there doesn't seem to be any way to use the latest version of spring boot with jpmml other than the package relocation you mentioned at the moment.

OK, if the Spring framework does not work with the org.glassfish.jaxb:jaxb-runtime:3.0.2 dependency, then we cannot use it.

The simplest workaround on the JPMML-Evaluator side is to simply switch Jakarta XML Bind implementations. Next to the (default-) org.jpmml:pmml-evaluator-metro dependency there is a org.jpmml:pmml-evaluator-moxy dependency, which relies on the EclipseLink MOXy implementation.

In this setup, JPMML-Evaluator and Spring framework would be sharing JAXB interfaces as defined by the jakarta.xml.bind:jakarta.xml.bind-api:3.0.1 dependency (interfaces should be backwards compatible). However, during application execution, JPMML-Evaluator would be using the latest EclipseLink MOXy implementation, whereas Spring framework would be using a legacy GlassFish Metro implementation.

The following dependency configuration works fine with your sample project:

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter</artifactId>
	</dependency>

	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<exclusions>
			<exclusion>
				<groupId>jakarta.xml.bind</groupId>
				<artifactId>jakarta.xml.bind-api</artifactId>
			</exclusion>
		</exclusions>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.jpmml</groupId>
		<artifactId>pmml-evaluator-moxy</artifactId>
		<version>1.6.3</version>
	</dependency>

	<!-- Override Jakarta XML Bind interface -->
	<dependency>
		<groupId>jakarta.xml.bind</groupId>
		<artifactId>jakarta.xml.bind-api</artifactId>
		<version>3.0.1</version>
	</dependency>
</dependencies>

@vruusmann
Copy link
Member

@doljae Perform the above dependency change, and try to run your Spring Boot application. Does the model loading succeed, or not?

There is a possibility that both components will be a little bit confused, because they see two JAXB implementations available at the same time, and cannot decide which one to use. In principle, it should be possible to "activate" one specific JAXB implementation (over all others) by specifying the jakarta.xml.bind.JAXBContextFactory Java system property.

@doljae
Copy link
Author

doljae commented May 18, 2022

@vruusmann
I tried your suggestion (use moxy, enforce jakarta.xml.bind-api 3.0.1), sadly it does not work 😓, I mean the error was occured during runtime(+running test cases), not compile time.
According to the test failure logs, there's are two kinds of logs were occured.

1

jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	at app//jakarta.xml.bind.ContextFinder.newInstance(ContextFinder.java:255)
	at app//jakarta.xml.bind.ContextFinder.newInstance(ContextFinder.java:243)

Maybe this error is occured because as you said, There is a possibility that both components will be a little bit confused.

2

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804)
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620)
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
...
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException
	at org.hibernate.boot.spi.XmlMappingBinderAccess.<init>(XmlMappingBinderAccess.java:43)
	at org.hibernate.boot.MetadataSources.getXmlMappingBinderAccess(MetadataSources.java:115)
	at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.prepare(MetadataBuildingProcess.java:110)
...
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)

This error means Spring ApplicationContext cannot create JPA related java beans, and the JPA implementations is Hibernate. I comment the stack overflow link which said Hibernate need javax.xml.* packages.

@doljae
Copy link
Author

doljae commented May 18, 2022

@vruusmann
And maybe the spring team's answer is this article below

According to this link & article in the comment, Spring projects will use old version Glassfish until the release of Spring 6 & Boot 3, which mean the easiest way for using jpmml with Spring is to use jpmml 1.5.x with pmml-extension I guess.

To be honest, I am a beginner in this area. So I don't know well.
If I want to use the latest version of jpmml, there seems to be no other way than package relocation as you said.

@vruusmann
Copy link
Member

1: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.

The problem is that LoadingModelEvaluatorBuilder is trying to locate the intended Jakarta XML Binding implementation in a very safe and stable way, using the Jakarta XML Binding interface methods:
https://github.com/jpmml/jpmml-evaluator/blob/1.6.3/pmml-evaluator/src/main/java/org/jpmml/evaluator/LoadingModelEvaluatorBuilder.java#L143

The org.jpmml.model.JAXBUtil#createUnmarshaller() utility method works fine if the application classpath has been set up orderly. Specifically, there is one conflict-free JAXB implemenntation available.

In the current case, it would be nice if we could tell to LoadingModelEvaluatorBuilder that we want to load the PMML XML document using EclipseLink MOXy unmarshaller.

Something like this would be ideal:

EvaluatorBuilder evaluatorBuilder = new LoadingModelEvaluatorBuilder()
  .setJAXBContext(org.eclipse.persistence.jaxb.JAXBContext.class)
  .load("mymodel.pmml");

Evaluator evaluator = evaluatorBuilder.build();
  1. java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

Looks like jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 and jakarta.xml.bind:jakarta.xml.bind-api:3.0.1 dependencies are not equivalent, because the former contains javax.xml.bind.* classes and the latter jakarta.xml.bind.* classes. I was fooled by the dependency prefix "jakarta".

In principle your project should include these two dependencies (the former for Spering framework, the latter for JPMML-Model/JPMML-Evaluator). Unfortunately, this is not technically possible, because dependency names (as defined by groupId plus artifactId) must be unique.

So, the solution is to replace the jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 dependency with some other (differently named-) dependency that contains all javax.xml.bind.* classes.

Maybe javax.xml.bind:jaxb-api:2.3.1 is the best option here.

@vruusmann
Copy link
Member

@doljae Looks like you need to stick with JPMML-Evaluator 1.5.X a little bit longer, until I extend the LoadingModelEvaluatorBuilder class with a setter for specifying the Jakarta XML Binding implementation class.

Will probably happen in the 1.6.4 release. However, the ETA of 1.6.4 is currently unknown.

@doljae
Copy link
Author

doljae commented May 19, 2022

@vruusmann
OK, I'll try to check if I'm following the context properly.

  1. Latest Spring related dependencies use javax.xml.bind.*, because Spring enforce to use org.glassfish.jaxb:jaxb-runtime:jar:2.3.6

  2. Latest jpmml (1.6.x) use jakarta.xml.bind-api:3.x.x, because it uses glassfish 3.x.x

  3. Current jpmml makes conflict if there are more than one implementation of JAXBContext.

  4. In this situation, latest jpmml use jakarta.xml.bind.*, so there are only one choice with Spring,
    enforce glassfish 3.x.x & add additional dependencies for api & implementation of javax.xml.bind.*

  5. Although you release jpmml 1.6.4 which has new feature: LoadingModelEvaluatorBuilder class with a setter for specifying the Jakarta XML Binding implementation class, using with spring project makes conflict because spring enforce to use org.glassfish.jaxb:jaxb-runtime:jar:2.3.6 and latest jpmml use jakarta.xml.bind.*, not javax.xml.bind.*

  6. If you make another type of jpmml for this situation(like metro, moxy), which has javax.xml.bind.*, maybe it does not conflict with Spring.
    (I wasn't talking about making something like this. I was just trying to make sure I understand what you're saying, and I hope you don't misunderstand me. Of course, if there is, many Spring users will be happy.)

@vruusmann
Copy link
Member

In comment #246 (comment) points 1, 2 and 4 are correct. Point 3 not so much.

Let's elaborate a bit further:

The *:bind-api dependency is responsible for defining JAXB interface classes, whereas the *:jaxb-runtime is responsible for defining JAXB implementation classes. They must always match at the major version level (2.3 interfaces with 2.3 runtime; 3.0 interfaces with 3.0 runtime).

The resolution of the JAXBContext class is initiated via JAXB interface methods:

  1. For 2.3 version it is javax.xml.bind.JAXBContext#newInstance().
  2. For 3.0 version it is jakarta.xml.bind.JAXBContext#newInstance().

Note the different package prefix javax vs jakarta!

Now in principle, it whould be possible to have JAXB 2.3 and 3.0 versions peacefully co-exist in one Java application. Spring framework is hard-coded to work with the 2.3 version, and JPMML-Evaluator with the 3.0 version!

That's the theory. In the current case the situation is a bit more complicated because we have a jakarta.xml.bind:jakarta.xml.bind-api which cannot be included twice into Apache Maven-managed classpath (we would like to include both of its 2.3 and 3.0 versions). We can work around this by replacing jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 with javax.xml.bind:jaxb-api:2.3.1.

@vruusmann
Copy link
Member

@doljae Can you develop your sample project a bit further to make it "do some Spring + PMML business logic" during unit testing?

It would be much easier to suggest classpath workarounds when I have an instant feedback mechanism available. Right now I must rely on your comments.

@doljae
Copy link
Author

doljae commented May 19, 2022

@vruusmann

Thank you for your support.
In fact, there is a security problem in the company that uses pmml, so I am worried about this part.
I will try to create a spring boot project based on maven and upload an example sample.

In fact, reproducing the error is simple. After writing only one class or method of jpmml as code, it can be reproduced by writing code using hibernate and DB of spring data jpa and testing, run and build.
(If the Glassfish version is not fixed to 3.0.x, a compilation error occurs. If fixed, a JAXBException occurs in JPA-related classes)

Anyway, I'll try to share a sample project as soon as possible. thank you.

@doljae
Copy link
Author

doljae commented May 19, 2022

@vruusmann
OK, I reproduced the error.
Repository link : https://github.com/doljae/jpmml-maven-test

You can reproduce the error by testing in this order.
The pom.xml is tuned the same as when using the metro dependency you gave.

  1. Execute docker/docker-compose.yml to load the MySQL container.
  2. Execute the EmployeeJpaTest.test() method.

This way I can reproduce the 2nd error I commented on.
(Of course, I have to use gradle, not maven, so I can't use a method to solve it using maven's some special plugins(if exists 😓 )

@vruusmann
Copy link
Member

Repository link : https://github.com/doljae/jpmml-maven-test

This repository still contains a deficient pom.xml.

However, it wasn't difficult to fix (as explained in #246 (comment)):

<dependencies>
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-test</artifactId>
		<!-- Free the 'jakarta.xml.bind:jakarta.xml.bind-api' dependency groupId:artifactId slot for re-definition -->
		<exclusions>
			<exclusion>
				<groupId>jakarta.xml.bind</groupId>
				<artifactId>jakarta.xml.bind-api</artifactId>
			</exclusion>
		</exclusions>
		<scope>test</scope>
	</dependency>

	<dependency>
		<groupId>org.jpmml</groupId>
		<artifactId>pmml-evaluator-metro</artifactId>
		<version>1.6.3</version>
	</dependency>

	<!-- Define Java XML Bind interface for Spring -->
	<dependency>
		<groupId>javax.xml.bind</groupId>
		<artifactId>jaxb-api</artifactId>
		<version>2.3.1</version>
	</dependency>

	<!-- Define Jakarta XML Bind interface for JPMML-Evaluator -->
	<dependency>
		<groupId>jakarta.xml.bind</groupId>
		<artifactId>jakarta.xml.bind-api</artifactId>
		<version>3.0.1</version>
	</dependency>
</dependencies>

Please note that I didn't need to explicitly define the org.glassfish.jaxb:jaxb-runtime:3.0.2 dependency, because it's coming in automatically via pmml-evaluator-metro.

After this change, the project builds and tests cleanly:

$ mvn clean install

[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] 
[INFO] --- maven-jar-plugin:3.2.2:jar (default-jar) @ demo ---
[INFO] Building jar: /tmp/jpmml-maven-test/target/demo-0.0.1-SNAPSHOT.jar
[INFO] 
[INFO] --- spring-boot-maven-plugin:2.6.7:repackage (repackage) @ demo ---
[INFO] Replacing main artifact with repackaged archive
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.479 s
[INFO] Finished at: 2022-05-19T19:12:09Z
[INFO] ------------------------------------------------------------------------

@doljae
Copy link
Author

doljae commented May 19, 2022

@vruusmann

OK, I tried using the pom.xml you commented and verified that my project works fine. I tried writing a gradle script with a similar configuration and it works equally well 🙂

The problem is that this is my test project and I still get the error in my main project 🙁

The error log is as follows.

[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	at app//org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at app//org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
	... 149 more
Caused by: jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
 - with linked exception:
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	at app//org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
	at app//org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:653)
	... 149 more
[java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory]
	at app//org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)
	at app//org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:486)
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1352)
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1195)
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:582)
	at app//org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542)
	at app//org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335)
	at app//org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at app//org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333)
	at app//org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208)
	at app//org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:276)
	at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1389)
	at app//org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1309)
	at app//org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:887)
	at app//org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:791)

It seems difficult for me to reproduce this in my test project. This error was occured when I try to add Evaluator implementation class to ApplicationContext.
This is sample code in my main project

@Bean
public Evaluator mainEvaluator() throws Exception {
    return loadEvaluator("XXXX.pmml"); // ->  exception point!, jakarta.xml.bind.JAXBException: Implementation of Jakarta XML Binding-API has not been found on module path or classpath.
}

private Evaluator loadEvaluator(String pmmlFileName){
    ...
    return evaluator;
}

I also leaves the state of the dependency tree for confirmation.

 ./gradlew :dependencyInsight --dependency javax.xml.bind                           

> Task :dependencyInsight
javax.xml.bind:jaxb-api:2.3.1 (selected by rule)
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

javax.xml.bind:jaxb-api:2.3.1
\--- org.hibernate:hibernate-core:5.4.33
     +--- org.springframework.boot:spring-boot-starter-data-jpa:2.5.8
     |    \--- compileClasspath (requested org.springframework.boot:spring-boot-starter-data-jpa)
     \--- org.hibernate:hibernate-envers:5.4.33
          \--- org.springframework.data:spring-data-envers:2.5.7 (requested org.hibernate:hibernate-envers:5.4.32.Final)
               \--- compileClasspath (requested org.springframework.data:spring-data-envers)

javax.xml.bind:jaxb-api -> 2.3.1
\--- compileClasspath

(*) - dependencies omitted (listed previously)
 ./gradlew :dependencyInsight --dependency jakarta.xml.bind

> Task :dependencyInsight
jakarta.xml.bind:jakarta.xml.bind-api:3.0.1 (selected by rule)
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

jakarta.xml.bind:jakarta.xml.bind-api:3.0.1
\--- org.jpmml:pmml-model:1.6.3
     +--- org.jpmml:pmml-evaluator:1.6.3
     |    \--- org.jpmml:pmml-evaluator-metro:1.6.3
     |         \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)
     \--- org.jpmml:pmml-model-metro:1.6.3
          \--- org.jpmml:pmml-evaluator-metro:1.6.3 (*)

jakarta.xml.bind:jakarta.xml.bind-api -> 3.0.1
\--- compileClasspath

jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 -> 3.0.1
\--- org.glassfish.jaxb:jaxb-runtime:2.3.5
     +--- org.jpmml:pmml-model-metro:1.6.3 (requested org.glassfish.jaxb:jaxb-runtime:3.0.2)
     |    \--- org.jpmml:pmml-evaluator-metro:1.6.3
     |         \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)
     \--- org.hibernate:hibernate-core:5.4.33 (requested org.glassfish.jaxb:jaxb-runtime:2.3.1)
          +--- org.springframework.boot:spring-boot-starter-data-jpa:2.5.8
          |    \--- compileClasspath (requested org.springframework.boot:spring-boot-starter-data-jpa)
          \--- org.hibernate:hibernate-envers:5.4.33
               \--- org.springframework.data:spring-data-envers:2.5.7 (requested org.hibernate:hibernate-envers:5.4.32.Final)
                    \--- compileClasspath (requested org.springframework.data:spring-data-envers)

(*) - dependencies omitted (listed previously)
 ./gradlew :dependencyInsight --dependency glassfish

> Task :dependencyInsight
org.glassfish.jaxb:jaxb-runtime:2.3.5 (selected by rule)
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

org.glassfish.jaxb:jaxb-runtime:2.3.1 -> 2.3.5
\--- org.hibernate:hibernate-core:5.4.33
     +--- org.springframework.boot:spring-boot-starter-data-jpa:2.5.8
     |    \--- compileClasspath (requested org.springframework.boot:spring-boot-starter-data-jpa)
     \--- org.hibernate:hibernate-envers:5.4.33
          \--- org.springframework.data:spring-data-envers:2.5.7 (requested org.hibernate:hibernate-envers:5.4.32.Final)
               \--- compileClasspath (requested org.springframework.data:spring-data-envers)

org.glassfish.jaxb:jaxb-runtime:3.0.2 -> 2.3.5
\--- org.jpmml:pmml-model-metro:1.6.3
     \--- org.jpmml:pmml-evaluator-metro:1.6.3
          \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)

org.glassfish.jaxb:txw2:2.3.5 (selected by rule)
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

org.glassfish.jaxb:txw2:2.3.5
\--- org.glassfish.jaxb:jaxb-runtime:2.3.5
     +--- org.jpmml:pmml-model-metro:1.6.3 (requested org.glassfish.jaxb:jaxb-runtime:3.0.2)
     |    \--- org.jpmml:pmml-evaluator-metro:1.6.3
     |         \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)
     \--- org.hibernate:hibernate-core:5.4.33 (requested org.glassfish.jaxb:jaxb-runtime:2.3.1)
          +--- org.springframework.boot:spring-boot-starter-data-jpa:2.5.8
          |    \--- compileClasspath (requested org.springframework.boot:spring-boot-starter-data-jpa)
          \--- org.hibernate:hibernate-envers:5.4.33
               \--- org.springframework.data:spring-data-envers:2.5.7 (requested org.hibernate:hibernate-envers:5.4.32.Final)
                    \--- compileClasspath (requested org.springframework.data:spring-data-envers)

(*) - dependencies omitted (listed previously)
 ./gradlew :dependencyInsight --dependency jpmml           

> Task :dependencyInsight
org.jpmml:pmml-evaluator:1.6.3
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

org.jpmml:pmml-evaluator:1.6.3
\--- org.jpmml:pmml-evaluator-metro:1.6.3
     \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)

org.jpmml:pmml-evaluator-metro:1.6.3 (selected by rule)
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

org.jpmml:pmml-evaluator-metro -> 1.6.3
\--- compileClasspath

org.jpmml:pmml-model:1.6.3
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

org.jpmml:pmml-model:1.6.3
+--- org.jpmml:pmml-evaluator:1.6.3
|    \--- org.jpmml:pmml-evaluator-metro:1.6.3
|         \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)
\--- org.jpmml:pmml-model-metro:1.6.3
     \--- org.jpmml:pmml-evaluator-metro:1.6.3 (*)

org.jpmml:pmml-model-metro:1.6.3
   variant "compile" [
      org.gradle.status              = release (not requested)
      org.gradle.usage               = java-api
      org.gradle.libraryelements     = jar (compatible with: classes)
      org.gradle.category            = library

      Requested attributes not found in the selected variant:
         org.gradle.dependency.bundling = external
         org.gradle.jvm.environment     = standard-jvm
         org.gradle.jvm.version         = 17
   ]

org.jpmml:pmml-model-metro:1.6.3
\--- org.jpmml:pmml-evaluator-metro:1.6.3
     \--- compileClasspath (requested org.jpmml:pmml-evaluator-metro)

(*) - dependencies omitted (listed previously)

@doljae
Copy link
Author

doljae commented May 20, 2022

@vruusmann

I reproduced the error which I mentioned in the previous comment.
You can reproduce the error by testing in this order. (same as before)

Repository link : https://github.com/doljae/jpmml-maven-test

  1. Execute docker/docker-compose.yml to load the MySQL container.
  2. Execute the EmployeeJpaTest.test() method.

I changed test configuration from @DataJpaTest to @SpringBootTest, which scan all Java bean from Configuration file(in this case, JPMMLConfiguration) and register it to ApplicationContext.

@vruusmann
Copy link
Member

java.lang.ClassNotFoundException: org.glassfish.jaxb.runtime.v2.ContextFactory
at app//org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:658)

It means that the Spring framework cannot find JAXB implementation classes.

Yesterday evening, I showed you how to fix your application classpath for JAXB interface classes. Now you need to do the same for JAXB implementations.

Looks like the application classpath only contains "new" JAXB implementation (org.glassfish.jaxb:jaxb-runtime:3.0.2), which is OK by JPMML-Evaluator, but is not OK for Spring framework.

Again, as explained in my above comments (yesterday), the easiest way to do so is to switch from GlassFish Metro to EclipseLink MOXy for the JPMML-Evaluator components. You haven't done so in your pom.xml yet.

@doljae
Copy link
Author

doljae commented May 20, 2022

@vruusmann

It is clear that I do not fully understand this classpath and xml related dependencies.

However, even if any of the latest versions of jpmml including jpmml-metro and jpmml-moxy are used, it seems to be necessary to exclude specific dependencies and add dependencies that provide separate apis and implementations to avoid conflicts with Spring. (at least 2 or more)

As I commented at the beginning, I can use the latest version of jpmml with Spring by adding a dependency that can catch errors that occur. (exclude xml api, add new xml api, add implementation, etc...)

However, I am skeptical that this is a good choice in my current development environment compared to using the 1.5 version. And this will be the same for users who use other Spring.

So I think it is a good choice to roll back to version 1.5 in my project environment considering the unpredictable side effects. As of Spring Boot 3, Jakarta.xml.bind.* classpath will be used to match related dependency compatibility, and it seems best for now to bring the latest jpmml version.

@doljae
Copy link
Author

doljae commented May 20, 2022

@vruusmann

Apart from this, it would be good to add a guide to jpmml version selection for users using the Spring project to the README. If it's okay, I'll post a PR on the relevant part.

@vruusmann
Copy link
Member

vruusmann commented May 20, 2022

it would be good to add a guide to jpmml version selection for users using the Spring project

This topic can be generalized further: "How to configure application classpath so that it would contains BOTH Java XML Binding (javax.xml.bind.*) and Jakarta XML Binding (jakarta.xml.bind.*) classes".

Your concern is about the Spring framework. Yesterday, when I was performing JPMML-SparkML library upgrade, then I found a similar classpath conflict in Apache Spark ML 3.X versions as well.

I'm currently thinking about doing a small blog post about diagnostics & workarounds & final resolution.

If it's okay, I'll post a PR on the relevant part.

You can drop your "condensed resolution" into this thread.

Later, it will be possible to pin this issue for better visibility for the next one-two years (until the world catches up with jakarta.xml.bind.* classes).

@doljae
Copy link
Author

doljae commented May 22, 2022

@vruusmann

Sorry for the late reply.

Later, it will be possible to pin this issue for better visibility for the next one-two years (until the world catches up with > jakarta.xml.bind.* classes).

I strongly agree with your opinion. I'm sure pinning this issue will help many jpmml users (including those using Spring).

To be honest, I didn't think I would be able to comment and support this issue for this long. Thank you for your active support 👍

@doljae
Copy link
Author

doljae commented May 22, 2022

For users who use Spring Framework...

  1. When the latest version of jpmml(1.6.x) coexists with Spring Framework 5 and Spring Boot 2 projects, an error occurs due to sub-dependency conflicts. Please refer to this issue for related history.

  2. After the release and migration of Spring Framework 6, Spring Boot 3, it is expected that there will be no conflicts with the latest version of jpmml. Until then, we recommend that you consider using the 1.5.x version.

@vruusmann vruusmann changed the title Jakarta XML Binding version conflict between JPMML-Evaluator and Spring framework Configuring classpath for a mixed use of Java XML Binding (javax.xml.bind.*) plus Jakarta XML Binding (jakarta.xml.bind.*) May 23, 2022
@vruusmann vruusmann pinned this issue May 23, 2022
@vruusmann
Copy link
Member

I maintain my position that it is possible for a single application to make use of both Java XML Binding and Jakarta XML Binding APIs, without any compile-time or run-time classpath conflicts.

Attached is a small demo project jaxb_demo.zip, which contains a small and self-contained proof about it.

Workflow:

  • Download jaxb_demo.zip.
  • Unzip jaxb_demo.zip, and step into the jaxb_demo directory.
  • Build using Apache Maven command mvn clean install.

Example runs:

  • Export the path to the Java SE home directory as JAVA_HOME environment variable. This demo project works fine with all Java 8 though 17 versions.
  • Execute $JAVA_HOME/bin/java -Djakarta.xml.bind.JAXBContextFactory=org.eclipse.persistence.jaxb.JAXBContextFactory -jar target/jaxb_demo-executable-1.0-SNAPSHOT.jar etc/DecisionTreeIris.pmml.

The demo application class jaxb.demo.Demo prints out the following information:

  1. Proof of a live javax.xml.bind.JAXBContext object instance - this is the entry point for Java XML Binding users.
  2. Proof of a live jakarta.xml.bind.JAXBContext object instance - this is the entry point for Jakarta XML Binding users.
  3. Proof of a live org.jpmml.evaluator.Evaluator object instance - this is the entry point for JPMML-Evaluator users.

A couple of things to pay attention to in jaxb_demo/pom.xml:

  • The Apache Maven classpath definition is sensitive towards the ordering of dependencies. For maximum simplicity, the JPMML-Evaluator dependency is defined before the Spring framework dependency.
  • The JPMML-Evaluator library is included as the org.jpmml:pmml-evaluator-moxy dependency. The intent is to bring in an alternative EclipseLink MOXy JAXB runtime, which is independent from the "default" GlassFish Metro JAXB runtime.
  • The Spring framework contains invalid Java XML Binding dependency declaration. This is manually corrected by replacing jakarta.xml.bind:jakarta.xml.bind-api:2.3.3 with javax.xml.bind:jaxb-api:2.3.1.
  • The EclipseLink MOXy JAXB runtime is activated by specifying the jakarta.xml.bind.JAXBContextFactory system property as org.eclipse.persistence.jaxb.JAXBContextFactory. This activation can be performed using Java.exe command-line options, or from within the Java application code using the System#setProperty(String, String) method.
  • If the EclipseLink MOXy JAXB runtime is not activated, then the second part of the demo application falls back to the default GlassFish Metro JAXB runtime, and unexpected things begin to happen.

jaxb_demo.zip
.

@doljae
Copy link
Author

doljae commented May 23, 2022

@vruusmann

I checked your demo project. I'm leaving a comment because I think you have some misunderstanding.

1. I didn't say that it is impossible to use Java XML Binding, Jakarta XML Binding API without conflict.

I also wrote in the first text of this issue that I left out that I solved the problem through a couple of dependency combinations. And with your explanation, I was able to raise my understanding and confirm it through the attached sample project. I learned a lot from your latest comment's explanation. 🙇

2. The latest version of current released jpmml has no problems by itself.

For projects that do not use frameworks or do not have complex dependency relationships in build scripts, it is expected that there will be no problems using the latest version of jpmml.

3. However, there is a clear possibility that manual operation will create conflicts with other code and dependencies.

As you better know, if it's a corporate project rather than an individual, small-scale project, you should be wary of work with a wide range of modifications. I think it's definitely justified to make a safer choice in this and this situation.

Personally, I prefer to use the latest version. If it was my personal project, I would have applied the method you suggested to use the latest version.

Anyway, it is true that it is inconvenient to use the latest version of jpmml from the perspective of a person using the Spring framework(or user who use some kind of dependency management plugins.
Dependencies that were used in one line become multiple lines, and some settings in the build script need to be modified as well. That's why in the previous comment, I left that it would be convenient to have a new jpmml-evaluator that uses the javax.xml.bind.* package, that is, uses the 2.3.x version of the glassfish runtime.

Anyway, I'm sure that your comment on mixed use of Java XML Binding plus Jakarta XML Binding in detail will be helpful to many users. 👍

@paranjapeved15
Copy link

@doljae what did you finally end up doing? I am also stuck in a similar situation with 1.6.4 jpmml-evaluator.

@doljae
Copy link
Author

doljae commented Jan 28, 2023

@doljae what did you finally end up doing? I am also stuck in a similar situation with 1.6.4 jpmml-evaluator.

Hello, @paranjapeved15 🙂
Like I said in my previous comment, I had an issue with the dependency management feature of the framework I was using in my project.

So I kept the existing 1.5.x. The core features of jpmml that I have been using are similar to the 1.5 and 1.6 versions, and I decided that it would be okay not to apply the latest version, including the above troubleshooting, to the project.

If you want to use the 1.6.x version and it is difficult to use 1.6.x directly in your development environment, get a maintainer's guide or make sure that you can use jakarta.xml.* sub-packages and classes in your project.
For the latter one, you will need to add a separate configuration or dependency.

FYI, if you're using Spring Boot, I know that the Boot 3 version of dependency management changed glassfish to using the jakarta package. So, if you use Boot 3 or higher, you will be able to use jpmml 1.6.x without any special problems.
Of course you need to check 👍

@paranjapeved15
Copy link

@doljae Looks like you need to stick with JPMML-Evaluator 1.5.X a little bit longer, until I extend the LoadingModelEvaluatorBuilder class with a setter for specifying the Jakarta XML Binding implementation class.

Will probably happen in the 1.6.4 release. However, the ETA of 1.6.4 is currently unknown.

@vruusmann did you extend the LoadingModelEvaluatorBuilder in 1.6.4?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants