java-client-api icon indicating copy to clipboard operation
java-client-api copied to clipboard

Request for Jakarta EE 10 compliant version

Open ralfhergert opened this issue 1 year ago • 18 comments

We are using the marklogic-java-client (https://github.com/marklogic/java-client-api) in an SpringBoot-application. And we would like to upgrade our application from SpringBoot 2.7 to 3. SpringBoot 3 is using the Spring Framework in version 6.0, which dropped the support of any javax-library in favor of Jakarta EE 10.

In Jakarta EE 10 all libraries and classes in javax have been moved into the jarkata-relam. For instance the library javax.xml.bind:jaxb-api is now jakarta.xml.bind:jakarta.xml.bind-api. Also classes have been moved, e.g. javax.xml.bind.JAXBContext is now jakarta.xml.bind.JAXBContext.

Here the marklogic-java-client is causing a compatiblity issue between javax.xml.bind:jaxb-api:2.3.1 and jakarta.xml.bind:jakarta.xml.bind-api:4.0.0.

We would kindly request a version of the marklogic-java-client which is using (and thus compatible with) Jakarta EE 10.

ralfhergert avatar Jul 24 '23 06:07 ralfhergert

Using Java 17, I created a SpringBoot 3 application that depends on Springframework 6 and Jakarta.xml.bind 4. I created this project by starting with https://spring.io/guides/gs/spring-boot/ & https://github.com/spring-guides/gs-spring-boot.git. Then I added a dependency on MarkLogic Client 6.2.2 and javax.xml.bind 2.3.1.

dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation "javax.xml.bind:jaxb-api:2.3.1" implementation "org.glassfish.jaxb:jaxb-runtime:2.3.2" implementation "org.glassfish.jaxb:jaxb-core:2.3.0.1" implementation "com.marklogic:marklogic-client-api:6.2.2" implementation 'org.w3c:dom:2.3.0-jaxb-1.0.6'

testImplementation 'org.springframework.boot:spring-boot-starter-test'

}

I continued by changing the default endpoint in HelloController.java, so that it would read a document from MarkLogic using a JAXBHandle, updating the resulting object, and writing the updated object as a new document.

package com.example.springboot;

import com.marklogic.client.DatabaseClient; import com.marklogic.client.DatabaseClientFactory; import com.marklogic.client.document.XMLDocumentManager; import com.marklogic.client.io.JAXBHandle; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController;

import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.annotation.XmlRootElement;

@RestController public class HelloController {

@GetMapping("/")
public String index() throws JAXBException {
	DatabaseClient client = DatabaseClientFactory.newClient(
			"localhost", 8055, new DatabaseClientFactory.DigestAuthContext("admin", "admin"));
	XMLDocumentManager XMLDocMgr = client.newXMLDocumentManager();

	JAXBContext jaxbContext = JAXBContext.newInstance(Product.class);
	@SuppressWarnings({ "rawtypes", "unchecked" })
	JAXBHandle jaxbH = new JAXBHandle(jaxbContext);
	XMLDocMgr.read("/product.xml", jaxbH);
	System.out.println(jaxbH.toString());

	((Product) jaxbH.get()).setName("Alex");
	((Product) jaxbH.get()).setIndustry("Bookkeeping");
	((Product) jaxbH.get()).setDescription("Librarian");
	XMLDocMgr.write("/productAlex.xml", jaxbH);

	client.release();
	return "Greetings from Spring Boot!";
}

@XmlRootElement
static public class Product {
	private String name;
	private String industry;
	private String description;
	public Product() {
		super();
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getIndustry() {
		return industry;
	}
	public void setIndustry(String industry) {
		this.industry = industry;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
}

}

All this works as expected when a request is sent to the endpoint. The original document (which I already had in the database) is read and printed to standard out, and a new document is created in the database.

Hopefully that helps with using the current client with SpringBoot 3.

BillFarber avatar Jul 24 '23 22:07 BillFarber

Hi Bill, this issue here was written as link to https://help.marklogic.com/Tickets/Ticket/View/35838. To put it simply javax.xml.bind:jaxb-api:2.3.1 is not Jakarta EE 10.

ralfhergert avatar Jul 25 '23 07:07 ralfhergert

To put it simply javax.xml.bind:jaxb-api:2.3.1 is not Jakarta EE 10.

Yes, but that does not mean that you can't use it. You just have to add it as a dependency as I did above. For more information on using the older version of JAXB with EE 10, please see https://github.com/marklogic/java-client-api#including-jaxb-support

BillFarber avatar Jul 25 '23 11:07 BillFarber

@ralfhergert What @BillFarber 's example verifies is that you can have a Spring Boot 3 / Spring 6 / Java EE 10 / Java 17+ application and still use the older javax libraries, as they won't conflict with the newer jakarta libraries. We are hoping that same approach will work for you as well.

rjrudin avatar Jul 25 '23 22:07 rjrudin

Hi @BillFarber, could you add a rest-controller-method in your example which returns a ResponseEntity<Product> ? That would be the point at which I'm struggling right now: it seems that spring-webmvc 6 cannot find an appropriate handler to map the JAXB2-annotated entity (javax.xml.*) into a character representation.

ralfhergert avatar Aug 03 '23 07:08 ralfhergert

@ralfhergert We'll get an example project posted soon - we just put one together to verify that Spring MVC 6 can deserialize an XML payload into a Product instance using jakarta.xml, and then the Java Client can serialize that to XML when that Product instance has a javax.xml annotation on it as well.

I definitely appreciate the ugliness and tedium of using both sets of annotations. We at least want to make sure that works fine via a working example, and then we're considering some options for how best to support jakarta.xml while still supporting Java 8 (which many customers still depend on).

rjrudin avatar Aug 03 '23 13:08 rjrudin

@ralfhergert Please try the project in the zip attached to this comment (note that there's not a root directory in the zip, so unzip it into a new project directory first). The README has instructions on how to test it out.

For the GET endpoint, I took the approach of using javax.xml and the Java Client to read an XML document from MarkLogic as a Product instance. And then the controller method relies on Spring Boot 3 to deserialize that into a String. While that enforces schema validation per the Product class, you could also just return the raw XML string from MarkLogic depending on your requirements.

spring-boot-java17.zip

rjrudin avatar Aug 03 '23 15:08 rjrudin

The downside of course to the approach in the example project is this:

@XmlRootElement
@javax.xml.bind.annotation.XmlRootElement
public class Product

And of course, if you're having to annotate methods/fields because the default names generated by JAXB aren't acceptable, the above will get annoying very quickly.

rjrudin avatar Aug 03 '23 16:08 rjrudin

Thank you, @rjrudin ! I will give it a try. I'm in vacation the next 2 weeks, so please don't mind if I don't reply rigth away.

ralfhergert avatar Aug 05 '23 06:08 ralfhergert

@ralfhergert Did the above approach work for you? We do hope to shift to either a minimum version of Java 11 or 17 soon, but like many vendors, we have a significant number of customers still on Java 8.

rjrudin avatar Sep 06 '23 15:09 rjrudin

Hi @rjrudin thanks for pinging. Yes, as far I can tell, the double annotation with jakata and javax does work in a sense that both the marklogic-client and springboot-3 can deal with the same POJOs. It is not a pretty solution, as the POJOs get very crowded for instance:

/**
 * This class describes the state of a bulk ingest execution
 */
@XmlRootElement(name = "execution", namespace = "")
@javax.xml.bind.annotation.XmlRootElement(name = "execution", namespace = "")
@XmlAccessorType(XmlAccessType.FIELD)
@javax.xml.bind.annotation.XmlAccessorType(javax.xml.bind.annotation.XmlAccessType.FIELD)
public class BulkIngestExecution {

    @XmlAttribute
    @javax.xml.bind.annotation.XmlAttribute
    private UUID id;
    private TargetDb targetDb;
    @XmlElement(name = "executionStatus")
    @javax.xml.bind.annotation.XmlElement(name = "executionStatus")
    private ExecutionStatus status = ExecutionStatus.NEW;
    private String result;
    private String resultDetails;

    @XmlJavaTypeAdapter(GlobalDateTimeAdapterJakarta.class)
    @javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter(GlobalDateTimeAdapter.class)
    @JsonSerialize(using = GlobalDateTimeSerializer.class)
    @JsonDeserialize(using = GlobalDateTimeDeserializer.class)
    private GlobalDateTime lastChanged;

    @XmlElementWrapper(name = "properties")
    @javax.xml.bind.annotation.XmlElementWrapper(name = "properties")
    @XmlElement(name = "property", type = Property.class)
    @javax.xml.bind.annotation.XmlElement(name = "property", type = Property.class)
    @JsonProperty
    @JsonInclude(value= JsonInclude.Include.NON_EMPTY, content=JsonInclude.Include.NON_NULL)
    private List<Property> properties;

    [...]

And the solution comes with a price-tag:

  • we need to double all our tests for verifying that we used the annotations correctly (JAXB-de/serialization-tests)
  • we need to double all our adaptor-classes to provide Javax- and Jakarta-compatible versions of it, of course we need to tests for both

More code/tests means higher costs for maintainance/refactoring and longer build-cycles.

I do understand that you need to support your customers still using Java8. So maybe forking is a strategy here to provide both at the same time: one fork using javax and another one using jakarta?

ralfhergert avatar Sep 08 '23 09:09 ralfhergert

@ralfhergert We're going to look soon into what a fork would entail - ideally, we can partition off all the javax-specific code into a small place and just have that be the one different thing across the two products.

rjrudin avatar Sep 08 '23 14:09 rjrudin

@ralfhergert an update on this - our current thought is that we would change the Java Client to depend on jakarta.xml.bind instead of javax.xml.bind. The Java Client would still run fine on Java 8 as version 3 of jakarta.xml runs on Java 8. This will likely be done via a major release, as this would cause breaks for users currently on Java 8 that are happily using javax.xml.bind.

Also, I noticed https://ralfhergert.github.io/beach-volleyball-scoring-sheet/ - very nice! I'm currently nursing a bad shoulder from playing volleyball, hoping to get back out there again soon.

rjrudin avatar Sep 12 '23 17:09 rjrudin

Thanks @rjrudin! Wow that is great news! I'm looking forward to it.

Get well soon! Beach Volleyball is great fun and I always enjoy playing it, even tough I no longer participate in tournaments. :) (I made the scoring sheet as a simpler alternative to some overly complex sheets.)

ralfhergert avatar Sep 13 '23 07:09 ralfhergert

Hi @rjrudin You created this feature-branch some time ago: https://github.com/marklogic/java-client-api/tree/feature/jakarta-xml Do you also plan to provide it as a release version? How about a dual release strategy in which a javax and a jakarta version are released in parallel? What do you think?

ralfhergert avatar Feb 06 '24 09:02 ralfhergert

It's certainly on our radar. Due to the presence of JAXB/Jakarta imports in public classes - JAXBHandle being one - we'd need to do a major release, so Java Client 7. That branch you point to I believe verifies that Java 8 could still be the minimum required version while using the Jakarta APIs, so we could simply switch to Jakarta and continue development on 7.x with no need for a separate release.

rjrudin avatar Feb 07 '24 13:02 rjrudin

@ralfhergert An update - we are tentatively planning a 7.0 release to coincide with MarkLogic 12 and any potentially breaking changes that may require. We will make the shift from javax.xml to jakarta.xml as part of that release as well.

rjrudin avatar Apr 08 '24 15:04 rjrudin

Thank you, @rjrudin That's great news! Looking forward to version 7.0 and ML12.

ralfhergert avatar Apr 09 '24 06:04 ralfhergert

Hi @rjrudin any chance that you create a release of https://github.com/marklogic/java-client-api/tree/feature/java-21? If not, a more detailed description how to build this project would be nice. Because right now it apprears to require an installed ML-server. Here my recommandation would be to use docker and the gradle plugin https://github.com/avast/gradle-docker-compose-plugin to start ML-server in a dockerized manner. (This is at least how we test our projects in conjunction with ML-server.) In some days this issue celebrates its first "birthday" - cake and baloons?

ralfhergert avatar Jul 12 '24 08:07 ralfhergert

Don't worry, we haven't forgotten this! We have a new tool being released soon that depends on the Java Client, and after that goes out, we'll be in good shape for a new 7.0 release of this that shifts the JAXB dependency from javax to jakarta.

We actually use Docker for almost every other tool/connector project; this project just haven't been converted over.

However, you should be able to run ./gradlew publishToMavenLocal locally to build the marklogic-client-api and publish it to ~/.m2/com/repository without running any tests and thus not having MarkLogic installed.

rjrudin avatar Jul 12 '24 13:07 rjrudin

@ralfhergert Are you using Maven or Gradle in your project to resolve dependencies? I ask because one option we're considering for a Java Client 7.0.0 release is using Gradle variants so that when you depend on marklogic-client-api, the Jakarta JAXB dependencies you get will depend on the version of Java you're using. Specifically, if you're using Java 8, you'll get Jakarta JAXB 3.x, and Java 9 or higher will get you Jakarta JAXB 4.x.

rjrudin avatar Jul 29 '24 16:07 rjrudin

Hi @rjrudin sounds great! Yes, we're using Gradle for building. (We are using Spring's dependency management plugin, which also effects what specific dependency versions are being pulled. Hope there will not be a conflict.) - Anyway, we are ready to give it a try. :)

ralfhergert avatar Jul 29 '24 20:07 ralfhergert

Confirming that this will be in the 7.0.0 release, which we are planning on releasing when the 12 EA version of the server is released.

rjrudin avatar Aug 01 '24 13:08 rjrudin

@ralfhergert This is finally addressed! I'm closing, though we are in the process of getting this published to Maven Central. Will notify you once it is there.

rjrudin avatar Aug 19 '24 13:08 rjrudin

7.0.0 is now available - https://repo1.maven.org/maven2/com/marklogic/marklogic-client-api/7.0.0/ .

rjrudin avatar Aug 19 '24 16:08 rjrudin