diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 96e7d2712..d097d6c60 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -11,7 +11,10 @@ okhttp = "5.0.0-alpha.7"
jmh = "1.37"
reactor = "3.6.2"
nullaway-core = "0.10.21"
-jaxb = "2.3.1"
+jaxb-api = "2.3.1"
+jaxb-impl = "2.3.8"
+jaxb-jakarta-api = "4.0.1"
+jaxb-jakarta-impl = "4.0.3"
moxy = "2.7.14"
jsoup = "1.17.2"
testng = "7.9.0"
@@ -61,7 +64,10 @@ okhttp-tls = { module = "com.squareup.okhttp3:okhttp-tls", version.ref = "okhttp
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "reactor" }
nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway-core" }
-jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "jaxb" }
+jaxb-api = { module = "javax.xml.bind:jaxb-api", version.ref = "jaxb-api" }
+jaxb-impl = { module = "com.sun.xml.bind:jaxb-impl", version.ref = "jaxb-impl" }
+jaxb-jakarta-api = { module = "jakarta.xml.bind:jakarta.xml.bind-api", version.ref = "jaxb-jakarta-api" }
+jaxb-jakarta-impl = { module = "com.sun.xml.bind:jaxb-impl", version.ref = "jaxb-jakarta-impl" }
moxy = { module = "org.eclipse.persistence:org.eclipse.persistence.moxy", version.ref = "moxy" }
jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" }
testng = { module = "org.testng:testng", version.ref = "testng" }
diff --git a/methanol-blackbox/build.gradle.kts b/methanol-blackbox/build.gradle.kts
index d4855d202..dba95ac78 100644
--- a/methanol-blackbox/build.gradle.kts
+++ b/methanol-blackbox/build.gradle.kts
@@ -13,6 +13,7 @@ dependencies {
testImplementation(project(":methanol-jackson-flux"))
testImplementation(project(":methanol-protobuf"))
testImplementation(project(":methanol-jaxb"))
+ testImplementation(project(":methanol-jaxb-jakarta"))
testImplementation(project(":methanol-brotli"))
testImplementation(project(":methanol-testing"))
testImplementation(libs.reactor.core)
@@ -20,6 +21,7 @@ dependencies {
testImplementation(libs.brotli.dec)
testImplementation(libs.reactivestreams)
testImplementation(libs.moxy)
+ testImplementation(libs.jaxb.jakarta.impl)
}
tasks.test {
@@ -30,7 +32,7 @@ tasks.test {
}
extraJavaModuleInfo {
- failOnMissingModuleInfo.set(false)
+ failOnMissingModuleInfo = false
automaticModule(libs.moxy.get().module.toString(), "org.eclipse.persistence.moxy")
}
diff --git a/methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/IntegrationTest.java b/methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/IntegrationTest.java
index 840d01a97..1681286cd 100644
--- a/methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/IntegrationTest.java
+++ b/methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/IntegrationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2023 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -55,6 +55,7 @@
import com.github.mizosoft.methanol.BodyDecoder;
import com.github.mizosoft.methanol.HttpReadTimeoutException;
import com.github.mizosoft.methanol.MediaType;
+import com.github.mizosoft.methanol.Methanol;
import com.github.mizosoft.methanol.MoreBodyPublishers;
import com.github.mizosoft.methanol.MultipartBodyPublisher;
import com.github.mizosoft.methanol.MultipartBodyPublisher.Part;
@@ -64,8 +65,13 @@
import com.github.mizosoft.methanol.blackbox.Bruh.BruhMoment;
import com.github.mizosoft.methanol.blackbox.Bruh.BruhMoments;
import com.github.mizosoft.methanol.blackbox.support.JacksonMapper;
-import com.github.mizosoft.methanol.testing.*;
+import com.github.mizosoft.methanol.testing.ByteBufferIterator;
+import com.github.mizosoft.methanol.testing.IterablePublisher;
+import com.github.mizosoft.methanol.testing.Logging;
+import com.github.mizosoft.methanol.testing.MockGzipMember;
import com.github.mizosoft.methanol.testing.MockGzipMember.CorruptionMode;
+import com.github.mizosoft.methanol.testing.RegistryFileTypeDetector;
+import com.github.mizosoft.methanol.testing.TestUtils;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@@ -98,13 +104,6 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.GZIPOutputStream;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBContextFactory;
-import javax.xml.bind.JAXBException;
-import javax.xml.bind.annotation.XmlAttribute;
-import javax.xml.bind.annotation.XmlElement;
-import javax.xml.bind.annotation.XmlElementWrapper;
-import javax.xml.bind.annotation.XmlRootElement;
import mockwebserver3.MockResponse;
import mockwebserver3.MockWebServer;
import okio.Buffer;
@@ -117,21 +116,24 @@
import org.junit.jupiter.api.io.TempDir;
import reactor.core.publisher.Flux;
-@Timeout(60)
+@Timeout(10)
class IntegrationTest {
static {
Logging.disable("com.github.mizosoft.methanol.internal.spi.ServiceCache");
}
- private static final Base64.Decoder BASE64_DEC = Base64.getDecoder();
+ private static final Base64.Decoder base64Decoder = Base64.getDecoder();
- private static final String poem =
+ private static final String POEM =
"Roses are red,\n"
+ "Violets are blue,\n"
+ "I hope my tests pass\n"
+ "I really hope they do";
- private static final String epicArtCourseXmlUtf8 =
+ // There's a slight distinction between how moxy's implementation of JavaEE's JAXB & glassfish's
+ // implementation of Jakarta's JAXB generate the XML, particularly in the XML declaration.
+
+ private static final String EPIC_ART_COURSE_JAVAX_XML_UTF_8 =
""
+ ""
+ ""
@@ -144,8 +146,28 @@ class IntegrationTest {
+ ""
+ "";
- private static final Course epicArtCourse =
- new Course(Type.ART, List.of(new Student("Leonardo Da Vinci"), new Student("Michelangelo")));
+ private static final String EPIC_ART_COURSE_JAKARTA_XML_UTF_8 =
+ ""
+ + ""
+ + ""
+ + ""
+ + "Leonardo Da Vinci"
+ + ""
+ + ""
+ + "Michelangelo"
+ + ""
+ + ""
+ + "";
+
+ private static final CourseJavax EPIC_ART_COURSE_JAVAX =
+ new CourseJavax(
+ Type.ART,
+ List.of(new StudentJavax("Leonardo Da Vinci"), new StudentJavax("Michelangelo")));
+
+ private static final CourseJakarta EPIC_ART_COURSE_JAKARTA =
+ new CourseJakarta(
+ Type.ART,
+ List.of(new StudentJakarta("Leonardo Da Vinci"), new StudentJakarta("Michelangelo")));
private static String lotsOfText;
private static Map poemEncodings;
@@ -160,8 +182,8 @@ class IntegrationTest {
private ScheduledExecutorService scheduler;
@BeforeAll
- static void readTestData() throws IOException {
- Class> cls = IntegrationTest.class;
+ static void readTestData() {
+ var cls = IntegrationTest.class;
lotsOfText = loadUtf8(cls, "/payload/alice.txt");
lotsOfJson = loadUtf8(cls, "/payload/lots_of_json.json");
@@ -183,10 +205,12 @@ static void readTestData() throws IOException {
"br", load(cls, "/payload/alice.br"),
"badzip", new byte[0]);
- var mapper = new JsonMapper();
- var type = new TypeRef>>() {};
try {
- lotsOfJsonDecoded = mapper.readerFor(mapper.constructType(type.type())).readValue(lotsOfJson);
+ var mapper = new JsonMapper();
+ lotsOfJsonDecoded =
+ mapper
+ .readerFor(mapper.constructType(new TypeRef>>() {}.type()))
+ .readValue(lotsOfJson);
} catch (IOException ioe) {
throw new UncheckedIOException(ioe);
}
@@ -201,15 +225,15 @@ static void readTestData() throws IOException {
@BeforeAll
static void registerJaxbImplementation() {
- System.setProperty(JAXBContext.JAXB_CONTEXT_FACTORY, MoxyJaxbContextFactory.class.getName());
+ System.setProperty(
+ javax.xml.bind.JAXBContext.JAXB_CONTEXT_FACTORY, MoxyJaxbContextFactory.class.getName());
}
@BeforeEach
void setUpLifecycle() throws IOException {
server = new MockWebServer();
server.start();
- var builder = HttpClient.newBuilder();
- client = builder.build();
+ client = Methanol.newBuilder().autoAcceptEncoding(false).build();
executor = Executors.newFixedThreadPool(8);
scheduler = Executors.newSingleThreadScheduledExecutor();
}
@@ -229,11 +253,11 @@ private void assertDecodes(String encoding, String expected, byte[] compressed)
}
private void assertDecodesSmall(String encoding) throws Exception {
- var compressed = BASE64_DEC.decode(poemEncodings.get(encoding));
- assertDecodes(encoding, poem, compressed);
+ var compressed = base64Decoder.decode(poemEncodings.get(encoding));
+ assertDecodes(encoding, POEM, compressed);
// Test deflate body without zlib wrapping
if (encoding.equals("deflate")) {
- assertDecodes(encoding, poem, zlibUnwrap(compressed));
+ assertDecodes(encoding, POEM, zlibUnwrap(compressed));
}
}
@@ -268,7 +292,7 @@ void decoding_brotli() throws Exception {
@Test
void decoding_concatenatedGzip() throws Exception {
var firstMember = lotsOfTextEncodings.get("gzip");
- var secondMember = BASE64_DEC.decode(poemEncodings.get("gzip"));
+ var secondMember = base64Decoder.decode(poemEncodings.get("gzip"));
var thirdMember =
MockGzipMember.newBuilder()
.addComment(55)
@@ -283,7 +307,7 @@ void decoding_concatenatedGzip() throws Exception {
server.enqueue(new MockResponse().setBody(buffer).setHeader("Content-Encoding", "gzip"));
var request = HttpRequest.newBuilder(server.url("/").uri()).build();
var response = client.send(request, decoding(ofString()));
- assertLinesMatch(lines(lotsOfText + poem + lotsOfText), lines(response.body()));
+ assertLinesMatch(lines(lotsOfText + POEM + lotsOfText), lines(response.body()));
}
@Test
@@ -291,7 +315,7 @@ void decoding_corruptConcatenatedGzip() {
var firstMember = lotsOfTextEncodings.get("gzip");
var secondMember =
MockGzipMember.newBuilder()
- .data(poem.getBytes(US_ASCII))
+ .data(POEM.getBytes(US_ASCII))
.corrupt(CorruptionMode.FLG, 0xE0) // add reserved flag
.build()
.getBytes();
@@ -325,7 +349,7 @@ void decoding_unsupported() {
void decoding_nestedHandlerGetsNoLengthOrEncoding() throws Exception {
server.enqueue(
new MockResponse()
- .setBody(okBuffer(BASE64_DEC.decode(poemEncodings.get("gzip"))))
+ .setBody(okBuffer(base64Decoder.decode(poemEncodings.get("gzip"))))
.setHeader("Content-Encoding", "gzip"));
var request = HttpRequest.newBuilder(server.url("/").uri()).build();
var headers = new AtomicReference();
@@ -337,7 +361,7 @@ void decoding_nestedHandlerGetsNoLengthOrEncoding() throws Exception {
headers.set(info.headers());
return BodyHandlers.ofString().apply(info);
}));
- assertEquals(poem, response.body());
+ assertEquals(POEM, response.body());
var headersMap = headers.get().map();
assertFalse(headersMap.containsKey("Content-Encoding"));
assertFalse(headersMap.containsKey("Content-Length"));
@@ -345,10 +369,10 @@ void decoding_nestedHandlerGetsNoLengthOrEncoding() throws Exception {
@Test
void decoding_noEncoding() throws Exception {
- server.enqueue(new MockResponse().setBody(poem));
+ server.enqueue(new MockResponse().setBody(POEM));
var request = HttpRequest.newBuilder(server.url("/").uri()).build();
var response = client.send(request, decoding(ofString()));
- assertEquals(poem, response.body());
+ assertEquals(POEM, response.body());
}
@Test
@@ -475,10 +499,10 @@ void ofObject_unsupported() {
@Test
void ofObject_stringDecoder() throws Exception {
- server.enqueue(new MockResponse().setBody(poem).addHeader("Content-Type", "text/plain"));
+ server.enqueue(new MockResponse().setBody(POEM).addHeader("Content-Type", "text/plain"));
var request = HttpRequest.newBuilder(server.url("/").uri()).build();
var response = client.send(request, ofObject(String.class));
- assertEquals(poem, response.body());
+ assertEquals(POEM, response.body());
}
@Test
@@ -515,12 +539,24 @@ void ofObject_uploadFlux() throws Exception {
void ofObject_downloadXml() throws Exception {
server.enqueue(
new MockResponse()
- .setBody(okBuffer(gzip(epicArtCourseXmlUtf8.getBytes(UTF_8))))
+ .setBody(okBuffer(gzip(EPIC_ART_COURSE_JAVAX_XML_UTF_8.getBytes(UTF_8))))
.addHeader("Content-Encoding", "gzip")
.addHeader("Content-Type", "application/xml"));
var request = MutableRequest.GET(server.url("/").uri());
- var response = client.send(request, decoding(ofObject(new TypeRef() {})));
- assertEquals(epicArtCourse, response.body());
+ var response = client.send(request, decoding(ofObject(new TypeRef() {})));
+ assertEquals(EPIC_ART_COURSE_JAVAX, response.body());
+ }
+
+ @Test
+ void ofObject_downloadXmlJakarta() throws Exception {
+ server.enqueue(
+ new MockResponse()
+ .setBody(okBuffer(gzip(EPIC_ART_COURSE_JAKARTA_XML_UTF_8.getBytes(UTF_8))))
+ .addHeader("Content-Encoding", "gzip")
+ .addHeader("Content-Type", "application/xml"));
+ var request = MutableRequest.GET(server.url("/").uri());
+ var response = client.send(request, decoding(ofObject(new TypeRef() {})));
+ assertEquals(EPIC_ART_COURSE_JAKARTA, response.body());
}
@Test
@@ -529,12 +565,28 @@ void ofObject_uploadXml() throws Exception {
var request =
MutableRequest.POST(
server.url("/").uri(),
- MoreBodyPublishers.ofObject(epicArtCourse, MediaType.TEXT_XML.withCharset(UTF_8)));
+ MoreBodyPublishers.ofObject(
+ EPIC_ART_COURSE_JAVAX, MediaType.TEXT_XML.withCharset(UTF_8)));
client.sendAsync(request, discarding());
var recordedRequest = server.takeRequest();
var uploaded = recordedRequest.getBody().readUtf8();
- assertEquals(epicArtCourseXmlUtf8, uploaded);
+ assertEquals(EPIC_ART_COURSE_JAVAX_XML_UTF_8, uploaded);
+ }
+
+ @Test
+ void ofObject_uploadXmlJakarta() throws Exception {
+ server.enqueue(new MockResponse());
+ var request =
+ MutableRequest.POST(
+ server.url("/").uri(),
+ MoreBodyPublishers.ofObject(
+ EPIC_ART_COURSE_JAKARTA, MediaType.TEXT_XML.withCharset(UTF_8)));
+ client.sendAsync(request, discarding());
+
+ var recordedRequest = server.takeRequest();
+ var uploaded = recordedRequest.getBody().readUtf8();
+ assertEquals(EPIC_ART_COURSE_JAKARTA_XML_UTF_8, uploaded);
}
@Test
@@ -575,7 +627,7 @@ void withReadTimeout_readThroughByteChannel() throws Exception {
var timeoutMillis = 50L;
server.enqueue(
new MockResponse()
- .setBody(poem)
+ .setBody(POEM)
.throttleBody(0, timeoutMillis * 10, TimeUnit.MILLISECONDS));
var request = HttpRequest.newBuilder(server.url("/").uri()).build();
var response =
@@ -768,29 +820,83 @@ static final class Tweet {
public Tweet() {}
}
- @XmlRootElement(name = "course")
- private static final class Course {
- @XmlAttribute(required = true)
+ @jakarta.xml.bind.annotation.XmlRootElement(name = "course")
+ private static final class CourseJakarta {
+ @jakarta.xml.bind.annotation.XmlAttribute(required = true)
+ private final Type type;
+
+ @jakarta.xml.bind.annotation.XmlElementWrapper(name = "enrolled-students")
+ @jakarta.xml.bind.annotation.XmlElement(name = "student")
+ private final List enrolledStudents;
+
+ CourseJakarta() {
+ this(Type.UNKNOWN, new ArrayList<>());
+ }
+
+ CourseJakarta(Type type, List enrolledStudents) {
+ this.type = type;
+ this.enrolledStudents = enrolledStudents;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof CourseJakarta
+ && type == ((CourseJakarta) obj).type
+ && enrolledStudents.equals(((CourseJakarta) obj).enrolledStudents);
+ }
+
+ @Override
+ public String toString() {
+ return "CourseJakarta[type=" + type + ", enrolledStudents=" + enrolledStudents + "]";
+ }
+ }
+
+ private static final class StudentJakarta {
+ @jakarta.xml.bind.annotation.XmlElement(required = true)
+ private final String name;
+
+ StudentJakarta() {
+ this("");
+ }
+
+ StudentJakarta(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof StudentJakarta && name.equals(((StudentJakarta) obj).name);
+ }
+
+ @Override
+ public String toString() {
+ return "StudentJakarta[" + name + "]";
+ }
+ }
+
+ @javax.xml.bind.annotation.XmlRootElement(name = "course")
+ private static final class CourseJavax {
+ @javax.xml.bind.annotation.XmlAttribute(required = true)
private final Type type;
- @XmlElementWrapper(name = "enrolled-students")
- @XmlElement(name = "student")
- private final List enrolledStudents;
+ @javax.xml.bind.annotation.XmlElementWrapper(name = "enrolled-students")
+ @javax.xml.bind.annotation.XmlElement(name = "student")
+ private final List enrolledStudents;
- private Course() {
+ CourseJavax() {
this(Type.UNKNOWN, new ArrayList<>());
}
- Course(Type type, List enrolledStudents) {
+ CourseJavax(Type type, List enrolledStudents) {
this.type = type;
this.enrolledStudents = enrolledStudents;
}
@Override
public boolean equals(Object obj) {
- return obj instanceof Course
- && type == ((Course) obj).type
- && enrolledStudents.equals(((Course) obj).enrolledStudents);
+ return obj instanceof CourseJavax
+ && type == ((CourseJavax) obj).type
+ && enrolledStudents.equals(((CourseJavax) obj).enrolledStudents);
}
@Override
@@ -799,21 +905,21 @@ public String toString() {
}
}
- private static final class Student {
- @XmlElement(required = true)
+ private static final class StudentJavax {
+ @javax.xml.bind.annotation.XmlElement(required = true)
private final String name;
- private Student() {
+ StudentJavax() {
this("");
}
- Student(String name) {
+ StudentJavax(String name) {
this.name = name;
}
@Override
public boolean equals(Object obj) {
- return obj instanceof Student && name.equals(((Student) obj).name);
+ return obj instanceof StudentJavax && name.equals(((StudentJavax) obj).name);
}
@Override
@@ -828,20 +934,21 @@ private enum Type {
CALCULUS
}
- public static class MoxyJaxbContextFactory implements JAXBContextFactory {
+ public static final class MoxyJaxbContextFactory implements javax.xml.bind.JAXBContextFactory {
public MoxyJaxbContextFactory() {}
@Override
- public JAXBContext createContext(Class>[] classesToBeBound, Map properties)
- throws JAXBException {
+ public javax.xml.bind.JAXBContext createContext(
+ Class>[] classesToBeBound, Map properties)
+ throws javax.xml.bind.JAXBException {
return org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(
classesToBeBound, properties);
}
@Override
- public JAXBContext createContext(
+ public javax.xml.bind.JAXBContext createContext(
String contextPath, ClassLoader classLoader, Map properties)
- throws JAXBException {
+ throws javax.xml.bind.JAXBException {
return org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(
contextPath, classLoader, properties);
}
diff --git a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbUtils.java b/methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/support/JaxbJakartaProviders.java
similarity index 50%
rename from methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbUtils.java
rename to methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/support/JaxbJakartaProviders.java
index d1df15e1e..c2b51c614 100644
--- a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbUtils.java
+++ b/methanol-blackbox/src/test/java/com/github/mizosoft/methanol/blackbox/support/JaxbJakartaProviders.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -20,35 +20,27 @@
* SOFTWARE.
*/
-package com.github.mizosoft.methanol.adapter.jaxb;
+package com.github.mizosoft.methanol.blackbox.support;
-import java.util.Map;
-import javax.xml.bind.JAXBContext;
-import javax.xml.bind.JAXBContextFactory;
-import javax.xml.bind.JAXBException;
+import com.github.mizosoft.methanol.BodyAdapter;
+import com.github.mizosoft.methanol.adapter.jaxb.jakarta.JaxbAdapterFactory;
-class JaxbUtils {
- static void registerImplementation() {
- System.setProperty(JAXBContext.JAXB_CONTEXT_FACTORY, MoxyJaxbContextFactory.class.getName());
- }
+public class JaxbJakartaProviders {
+ private JaxbJakartaProviders() {}
- // Make the factory accessible to JAXB
- public static final class MoxyJaxbContextFactory implements JAXBContextFactory {
- public MoxyJaxbContextFactory() {}
+ public static class EncoderProvider {
+ private EncoderProvider() {}
- @Override
- public JAXBContext createContext(Class>[] classesToBeBound, Map properties)
- throws JAXBException {
- return org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(
- classesToBeBound, properties);
+ public static BodyAdapter.Encoder provider() {
+ return JaxbAdapterFactory.createEncoder();
}
+ }
+
+ public static class DecoderProvider {
+ private DecoderProvider() {}
- @Override
- public JAXBContext createContext(
- String contextPath, ClassLoader classLoader, Map properties)
- throws JAXBException {
- return org.eclipse.persistence.jaxb.JAXBContextFactory.createContext(
- contextPath, classLoader, properties);
+ public static BodyAdapter.Decoder provider() {
+ return JaxbAdapterFactory.createDecoder();
}
}
}
diff --git a/methanol-blackbox/src/test/java/module-info.java b/methanol-blackbox/src/test/java/module-info.java
index a9a4c227a..5b015c9ce 100644
--- a/methanol-blackbox/src/test/java/module-info.java
+++ b/methanol-blackbox/src/test/java/module-info.java
@@ -5,6 +5,7 @@
import com.github.mizosoft.methanol.blackbox.support.FailingBodyDecoderFactory;
import com.github.mizosoft.methanol.blackbox.support.JacksonFluxProviders;
import com.github.mizosoft.methanol.blackbox.support.JacksonProviders;
+import com.github.mizosoft.methanol.blackbox.support.JaxbJakartaProviders;
import com.github.mizosoft.methanol.blackbox.support.JaxbProviders;
import com.github.mizosoft.methanol.blackbox.support.MyBodyDecoderFactory;
import com.github.mizosoft.methanol.blackbox.support.ProtobufProviders;
@@ -18,6 +19,7 @@
requires methanol.adapter.jackson.flux;
requires methanol.adapter.protobuf;
requires methanol.adapter.jaxb;
+ requires methanol.adapter.jaxb.jakarta;
requires methanol.brotli;
requires methanol.testing;
requires com.google.protobuf;
@@ -26,8 +28,11 @@
requires okio;
requires reactor.core;
requires org.reactivestreams;
- requires org.eclipse.persistence.moxy;
- requires java.sql; // Required by org.eclipse.persistence.moxy
+ requires java.xml.bind;
+ requires jakarta.xml.bind;
+ requires org.eclipse.persistence.moxy; // Used for Javax JAXB.
+ requires com.sun.xml.bind; // Used for Jakarta JAXB.
+ requires java.sql; // Required by org.eclipse.persistence.moxy.
requires static org.checkerframework.checker.qual;
provides BodyDecoder.Factory with
@@ -40,12 +45,14 @@
JacksonProviders.EncoderProvider,
ProtobufProviders.EncoderProvider,
JaxbProviders.EncoderProvider,
+ JaxbJakartaProviders.EncoderProvider,
CharSequenceEncoderProvider;
provides BodyAdapter.Decoder with
JacksonFluxProviders.DecoderProvider,
JacksonProviders.DecoderProvider,
ProtobufProviders.DecoderProvider,
JaxbProviders.DecoderProvider,
+ JaxbJakartaProviders.DecoderProvider,
StringDecoderProvider;
provides FileTypeDetector with
RegistryFileTypeDetectorProvider;
diff --git a/methanol-jaxb-jakarta/README.md b/methanol-jaxb-jakarta/README.md
new file mode 100644
index 000000000..9f4bdc4ea
--- /dev/null
+++ b/methanol-jaxb-jakarta/README.md
@@ -0,0 +1,184 @@
+# methanol-jaxb-jakarta
+
+Adapters for XML using Jakarta EE's [JAXB][jaxb].
+
+## Installation
+
+### Gradle
+
+```gradle
+implementation 'com.github.mizosoft.methanol:methanol-jaxb-jakarta:1.7.0'
+```
+
+### Maven
+
+```xml
+
+ com.github.mizosoft.methanol
+ methanol-jaxb-jakarta
+ 1.7.0
+
+```
+
+The adapters need to be registered as [service providers][serviceloader_javadoc] so Methanol knows
+they're there.
+The way this is done depends on your project setup.
+
+### Module Path
+
+Follow these steps if your project uses the Java module system.
+
+1. Add this class to your module:
+
+ ```java
+ public class JaxbProviders {
+ public static class EncoderProvider {
+ public static BodyAdapter.Encoder provider() {
+ return JaxbAdapterFactory.createEncoder();
+ }
+ }
+
+ public static class DecoderProvider {
+ public static BodyAdapter.Decoder provider() {
+ return JaxbAdapterFactory.createDecoder();
+ }
+ }
+ }
+ ```
+
+2. Add the corresponding provider declarations in your `module-info.java` file.
+
+ ```java
+ requires methanol.adapter.jaxb.jakarta;
+
+ provides BodyAdapter.Encoder with JaxbProviders.EncoderProvider;
+ provides BodyAdapter.Decoder with JaxbProviders.DecoderProvider;
+ ```
+
+### Classpath
+
+Registering adapters from the classpath requires declaring the implementation classes in
+provider-configuration
+files that are bundled with your JAR. You'll first need to implement
+delegating `Encoder` & `Decoder`
+that forward to the instances created by `JaxbAdapterFactory`. Extending from `ForwardingEncoder` &
+`ForwardingDecoder` makes this easier.
+
+You can use Google's [AutoService][autoservice] to generate the provider-configuration files
+automatically,
+so you won't bother writing them.
+
+#### Using AutoService
+
+First, [install AutoService][autoservice_getting_started].
+
+##### Gradle
+
+```gradle
+implementation "com.google.auto.service:auto-service-annotations:$autoServiceVersion"
+annotationProcessor "com.google.auto.service:auto-service:$autoServiceVersion"
+```
+
+##### Maven
+
+```xml
+
+ com.google.auto.service
+ auto-service-annotations
+ ${autoServiceVersion}
+
+```
+
+Configure the annotation processor with the compiler plugin.
+
+```xml
+
+ maven-compiler-plugin
+
+
+
+ com.google.auto.service
+ auto-service
+ ${autoServiceVersion}
+
+
+
+
+```
+
+Next, add this class to your project:
+
+```java
+public class JaxbAdapters {
+ @AutoService(BodyAdapter.Encoder.class)
+ public static class Encoder extends ForwardingEncoder {
+ public Encoder() {
+ super(JaxbAdapterFactory.createEncoder());
+ }
+ }
+
+ @AutoService(BodyAdapter.Decoder.class)
+ public static class Decoder extends ForwardingDecoder {
+ public Decoder() {
+ super(JaxbAdapterFactory.createDecoder());
+ }
+ }
+}
+```
+
+#### Manual Configuration
+
+You can also write the configuration files manually. First, add this class to your project:
+
+```java
+public class JaxbAdapters {
+ public static class Encoder extends ForwardingEncoder {
+ public Encoder() {
+ super(JaxbAdapterFactory.createEncoder());
+ }
+ }
+
+ public static class Decoder extends ForwardingDecoder {
+ public Decoder() {
+ super(JaxbAdapterFactory.createDecoder());
+ }
+ }
+}
+```
+
+Next, create two provider-configuration files in the resource directory: `META-INF/services`,
+one for the encoder and the other for the decoder. Each file must contain the fully qualified
+name of the implementation class.
+
+Let's say the above class is in a package named `com.example`. You'll want to have one file for the
+encoder named:
+
+```
+META-INF/services/com.github.mizosoft.methanol.BodyAdapter$Encoder
+```
+
+and contains the following line:
+
+```
+com.example.JaxbAdapters$Encoder
+```
+
+Similarly, the decoder's file is named:
+
+```
+META-INF/services/com.github.mizosoft.methanol.BodyAdapter$Decoder
+```
+
+and contains:
+
+```
+com.example.JaxbAdapters$Decoder
+```
+
+[jaxb]: https://eclipse-ee4j.github.io/jaxb-ri/
+
+[autoservice]: https://github.com/google/auto/tree/master/service
+
+[autoservice_getting_started]: https://github.com/google/auto/tree/master/service#getting-started
+
+[serviceloader_javadoc]: https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/ServiceLoader.html
diff --git a/methanol-jaxb-jakarta/build.gradle.kts b/methanol-jaxb-jakarta/build.gradle.kts
new file mode 100644
index 000000000..842e53451
--- /dev/null
+++ b/methanol-jaxb-jakarta/build.gradle.kts
@@ -0,0 +1,15 @@
+plugins {
+ id("conventions.java-library")
+ id("conventions.static-analysis")
+ id("conventions.testing")
+ id("conventions.coverage")
+ id("conventions.publishing")
+}
+
+dependencies {
+ api(project(":methanol"))
+ api(libs.jaxb.jakarta.api)
+
+ testImplementation(project(":methanol-testing"))
+ testImplementation(libs.jaxb.jakarta.impl)
+}
diff --git a/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/CachingJaxbBindingFactory.java b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/CachingJaxbBindingFactory.java
new file mode 100644
index 000000000..aacbf7649
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/CachingJaxbBindingFactory.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import jakarta.xml.bind.JAXBContext;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.Unmarshaller;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+final class CachingJaxbBindingFactory implements JaxbBindingFactory {
+ private final ConcurrentMap, JAXBContext> cachedContexts = new ConcurrentHashMap<>();
+
+ CachingJaxbBindingFactory() {}
+
+ @Override
+ public Marshaller createMarshaller(Class> boundClass) throws JAXBException {
+ return getOrCreateContext(boundClass).createMarshaller();
+ }
+
+ @Override
+ public Unmarshaller createUnmarshaller(Class> boundClass) throws JAXBException {
+ return getOrCreateContext(boundClass).createUnmarshaller();
+ }
+
+ // Visible for testing.
+ JAXBContext getOrCreateContext(Class> boundClass) {
+ return cachedContexts.computeIfAbsent(
+ boundClass,
+ c -> {
+ try {
+ return JAXBContext.newInstance(c);
+ } catch (JAXBException e) {
+ throw new UncheckedJaxbException(e);
+ }
+ });
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbAdapter.java b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbAdapter.java
new file mode 100644
index 000000000..fabe6e9de
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbAdapter.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import static java.util.Objects.requireNonNull;
+
+import com.github.mizosoft.methanol.BodyAdapter;
+import com.github.mizosoft.methanol.MediaType;
+import com.github.mizosoft.methanol.TypeRef;
+import com.github.mizosoft.methanol.adapter.AbstractBodyAdapter;
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.Unmarshaller;
+import jakarta.xml.bind.annotation.XmlEnum;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import jakarta.xml.bind.annotation.XmlType;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.http.HttpRequest.BodyPublisher;
+import java.net.http.HttpRequest.BodyPublishers;
+import java.net.http.HttpResponse.BodySubscriber;
+import java.net.http.HttpResponse.BodySubscribers;
+import java.nio.charset.Charset;
+import java.util.function.Supplier;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+abstract class JaxbAdapter extends AbstractBodyAdapter {
+ final JaxbBindingFactory jaxbFactory;
+
+ JaxbAdapter(JaxbBindingFactory jaxbFactory) {
+ super(MediaType.APPLICATION_XML, MediaType.TEXT_XML);
+ this.jaxbFactory = requireNonNull(jaxbFactory);
+ }
+
+ @Override
+ public boolean supportsType(TypeRef> type) {
+ if (!(type.type() instanceof Class>)) {
+ return false;
+ }
+
+ var clazz = type.rawType();
+ return clazz.isAnnotationPresent(XmlRootElement.class)
+ || clazz.isAnnotationPresent(XmlType.class)
+ || clazz.isAnnotationPresent(XmlEnum.class);
+ }
+
+ static final class Encoder extends JaxbAdapter implements BodyAdapter.Encoder {
+ Encoder(JaxbBindingFactory factory) {
+ super(factory);
+ }
+
+ @Override
+ public BodyPublisher toBody(Object object, @Nullable MediaType mediaType) {
+ requireNonNull(object);
+ requireSupport(object.getClass());
+ requireCompatibleOrNull(mediaType);
+ var outputBuffer = new ByteArrayOutputStream();
+ try {
+ var marshaller = jaxbFactory.createMarshaller(object.getClass());
+ String encoding;
+ if (mediaType != null && (encoding = mediaType.parameters().get("charset")) != null) {
+ marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
+ }
+ marshaller.marshal(object, outputBuffer);
+ } catch (JAXBException e) {
+ throw new UncheckedJaxbException(e);
+ }
+ return attachMediaType(BodyPublishers.ofByteArray(outputBuffer.toByteArray()), mediaType);
+ }
+ }
+
+ static final class Decoder extends JaxbAdapter implements BodyAdapter.Decoder {
+ Decoder(JaxbBindingFactory factory) {
+ super(factory);
+ }
+
+ @Override
+ public BodySubscriber toObject(TypeRef objectType, @Nullable MediaType mediaType) {
+ requireNonNull(objectType);
+ requireSupport(objectType);
+ requireCompatibleOrNull(mediaType);
+ var elementClass = objectType.exactRawType();
+ var charset = charsetOrNull(mediaType);
+ var unmarshaller = createUnmarshallerUnchecked(elementClass);
+ return BodySubscribers.mapping(
+ BodySubscribers.ofByteArray(),
+ bytes ->
+ unmarshalValue(elementClass, unmarshaller, new ByteArrayInputStream(bytes), charset));
+ }
+
+ @Override
+ public BodySubscriber> toDeferredObject(
+ TypeRef objectType, @Nullable MediaType mediaType) {
+ requireNonNull(objectType);
+ requireSupport(objectType);
+ requireCompatibleOrNull(mediaType);
+ var elementClass = objectType.exactRawType();
+ var charset = charsetOrNull(mediaType);
+ var unmarshaller = createUnmarshallerUnchecked(elementClass);
+ return BodySubscribers.mapping(
+ BodySubscribers.ofInputStream(),
+ in -> () -> unmarshalValue(elementClass, unmarshaller, in, charset));
+ }
+
+ private T unmarshalValue(
+ Class elementClass,
+ Unmarshaller unmarshaller,
+ InputStream in,
+ @Nullable Charset charset) {
+ try {
+ // If the charset is known from the media type, use it for a Reader
+ // to avoid the overhead of having to infer it from the document.
+ return elementClass.cast(
+ charset != null
+ ? unmarshaller.unmarshal(new InputStreamReader(in, charset))
+ : unmarshaller.unmarshal(in));
+ } catch (JAXBException e) {
+ throw new UncheckedJaxbException(e);
+ }
+ }
+
+ private Unmarshaller createUnmarshallerUnchecked(Class> elementClass) {
+ try {
+ return jaxbFactory.createUnmarshaller(elementClass);
+ } catch (JAXBException e) {
+ throw new UncheckedJaxbException(e);
+ }
+ }
+
+ private static @Nullable Charset charsetOrNull(@Nullable MediaType mediaType) {
+ return mediaType != null ? mediaType.charset().orElse(null) : null;
+ }
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbAdapterFactory.java b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbAdapterFactory.java
new file mode 100644
index 000000000..e01910340
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbAdapterFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import com.github.mizosoft.methanol.BodyAdapter.Decoder;
+import com.github.mizosoft.methanol.BodyAdapter.Encoder;
+
+/** Creates {@link com.github.mizosoft.methanol.BodyAdapter} implementations for XML using JAXB. */
+public class JaxbAdapterFactory {
+ private JaxbAdapterFactory() {}
+
+ /** Returns a new {@code Encoder} using the default caching factory. */
+ public static Encoder createEncoder() {
+ return createEncoder(JaxbBindingFactory.create());
+ }
+
+ /** Returns a new {@code Encoder} using the given factory. */
+ public static Encoder createEncoder(JaxbBindingFactory jaxbFactory) {
+ return new JaxbAdapter.Encoder(jaxbFactory);
+ }
+
+ /** Returns a new {@code Decoder} using the default caching factory. */
+ public static Decoder createDecoder() {
+ return createDecoder(JaxbBindingFactory.create());
+ }
+
+ /** Returns a new {@code Decoder} using the given factory. */
+ public static Decoder createDecoder(JaxbBindingFactory jaxbFactory) {
+ return new JaxbAdapter.Decoder(jaxbFactory);
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbBindingFactory.java b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbBindingFactory.java
new file mode 100644
index 000000000..467499c58
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbBindingFactory.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.Unmarshaller;
+
+/**
+ * Creates new {@link Marshaller} or {@link Unmarshaller} objects on demand for use by an adapter.
+ */
+public interface JaxbBindingFactory {
+ /** Returns a new {@code Marshaller} for encoding an object of the given class. */
+ Marshaller createMarshaller(Class> boundClass) throws JAXBException;
+
+ /** Returns a new {@code Unmarshaller} for decoding to an object of the given class. */
+ Unmarshaller createUnmarshaller(Class> boundClass) throws JAXBException;
+
+ /**
+ * Returns a new {@code JaxbBindingFactory} that creates and caches {@code JAXBContexts} for each
+ * requested type.
+ */
+ static JaxbBindingFactory create() {
+ return new CachingJaxbBindingFactory();
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/UncheckedJaxbException.java b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/UncheckedJaxbException.java
new file mode 100644
index 000000000..649cf39ec
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/UncheckedJaxbException.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import jakarta.xml.bind.JAXBException;
+
+/** Unchecked wrapper over a {@link JAXBException}. */
+public class UncheckedJaxbException extends RuntimeException {
+ /** Creates a new {@code UncheckedJaxbException} with the given cause. */
+ public UncheckedJaxbException(JAXBException cause) {
+ super(cause);
+ }
+
+ @Override
+ public JAXBException getCause() {
+ return (JAXBException) super.getCause();
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/main/java/module-info.java b/methanol-jaxb-jakarta/src/main/java/module-info.java
new file mode 100644
index 000000000..2b1d04305
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/main/java/module-info.java
@@ -0,0 +1,16 @@
+import com.github.mizosoft.methanol.BodyAdapter;
+import com.github.mizosoft.methanol.adapter.jaxb.jakarta.JaxbAdapterFactory;
+
+/**
+ * Provides {@link BodyAdapter.Encoder} and {@link BodyAdapter.Decoder} implementations for XML
+ * using JAXB. Note that, for the sake of configurability, the adapters are not service-provided by
+ * default. You will need to explicitly declare service-providers that delegate to the instances
+ * created by {@link JaxbAdapterFactory}.
+ */
+module methanol.adapter.jaxb.jakarta {
+ requires transitive methanol;
+ requires transitive jakarta.xml.bind;
+ requires static org.checkerframework.checker.qual;
+
+ exports com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+}
diff --git a/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/CachingJaxbBindingFactoryTest.java b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/CachingJaxbBindingFactoryTest.java
new file mode 100644
index 000000000..436e76283
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/CachingJaxbBindingFactoryTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+class CachingJaxbBindingFactoryTest {
+ @Test
+ void cachesContextsForSameType() {
+ var factory = new CachingJaxbBindingFactory();
+ assertThat(factory.getOrCreateContext(Point.class))
+ .isSameAs(factory.getOrCreateContext(Point.class));
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbDecoderTest.java b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbDecoderTest.java
new file mode 100644
index 000000000..a6e45b018
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbDecoderTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import static com.github.mizosoft.methanol.adapter.jaxb.jakarta.JaxbAdapterFactory.createDecoder;
+import static com.github.mizosoft.methanol.testing.verifiers.Verifiers.verifyThat;
+import static java.nio.charset.StandardCharsets.UTF_16;
+
+import com.github.mizosoft.methanol.testing.TestException;
+import org.junit.jupiter.api.Test;
+
+class JaxbDecoderTest {
+ @Test
+ void compatibleMediaTypes() {
+ verifyThat(createDecoder())
+ .isCompatibleWith("application/xml")
+ .isCompatibleWith("text/xml")
+ .isCompatibleWith("application/*")
+ .isCompatibleWith("text/*")
+ .isCompatibleWith("*/*");
+ }
+
+ @Test
+ void incompatibleMediaTypes() {
+ verifyThat(createDecoder())
+ .isNotCompatibleWith("text/html")
+ .isNotCompatibleWith("application/json")
+ .isNotCompatibleWith("image/*");
+ }
+
+ @Test
+ void deserialize() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withBody("")
+ .succeedsWith(new Point(1, 2));
+ }
+
+ @Test
+ void deserializeWithUtf16_inferredFromMediaType() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withMediaType("application/xml; charset=utf-16")
+ .withBody("", UTF_16)
+ .succeedsWith(new Point(1, 2));
+ }
+
+ @Test
+ void deserializeWithUtf16_inferredFromXmlDocument() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withBody("", UTF_16)
+ .succeedsWith(new Point(1, 2));
+ }
+
+ @Test
+ void deserializeList() {
+ verifyThat(createDecoder())
+ .converting(PointList.class)
+ .withBody(
+ ""
+ + ""
+ + ""
+ + ""
+ + "")
+ .succeedsWith(new PointList(new Point(1, 2), new Point(3, 4)));
+ }
+
+ @Test
+ void deserializeBadXml() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withBody("") // Missing forward slash
+ .failsWith(
+ UncheckedJaxbException.class); // JAXBExceptions are rethrown as UncheckedJaxbExceptions
+ }
+
+ @Test
+ void deserializeWithError() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withFailure(new TestException())
+ .failsWith(TestException.class);
+ }
+
+ @Test
+ void deserializeWithUnsupportedType() {
+ class NotAXmlRootElement {}
+
+ verifyThat(createDecoder()).converting(NotAXmlRootElement.class).isNotSupported();
+ }
+
+ @Test
+ void deserializeWithUnsupportedMediaType() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withMediaType("application/json")
+ .isNotSupported();
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbDeferredDecoderTest.java b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbDeferredDecoderTest.java
new file mode 100644
index 000000000..6082dd05c
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbDeferredDecoderTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import static com.github.mizosoft.methanol.adapter.jaxb.jakarta.JaxbAdapterFactory.createDecoder;
+import static com.github.mizosoft.methanol.testing.verifiers.Verifiers.verifyThat;
+import static java.nio.charset.StandardCharsets.UTF_16;
+
+import com.github.mizosoft.methanol.testing.TestException;
+import org.junit.jupiter.api.Test;
+
+class JaxbDeferredDecoderTest {
+ @Test
+ void deserialize() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withDeferredBody("")
+ .succeedsWith(new Point(1, 2));
+ }
+
+ @Test
+ void deserializeWithUtf16_inferredFromMediaType() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withMediaType("application/xml; charset=utf-16")
+ .withDeferredBody("", UTF_16)
+ .succeedsWith(new Point(1, 2));
+ }
+
+ @Test
+ void deserializeWithUtf16_inferredFromXmlDocument() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withDeferredBody(
+ "", UTF_16)
+ .succeedsWith(new Point(1, 2));
+ }
+
+ @Test
+ void deserializeList() {
+ verifyThat(createDecoder())
+ .converting(PointList.class)
+ .withDeferredBody(
+ ""
+ + ""
+ + ""
+ + ""
+ + "")
+ .succeedsWith(new PointList(new Point(1, 2), new Point(3, 4)));
+ }
+
+ @Test
+ void deserializeBadXml() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withDeferredBody(
+ "") // No enclosing forward slash
+ .failsWith(UncheckedJaxbException.class);
+ }
+
+ @Test
+ void deserializeWithError() {
+ verifyThat(createDecoder())
+ .converting(Point.class)
+ .withDeferredFailure(new TestException())
+ .failsWith(UncheckedJaxbException.class);
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbEncoderTest.java b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbEncoderTest.java
new file mode 100644
index 000000000..a8f94e5c5
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/JaxbEncoderTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import static com.github.mizosoft.methanol.adapter.jaxb.jakarta.JaxbAdapterFactory.createEncoder;
+import static com.github.mizosoft.methanol.testing.verifiers.Verifiers.verifyThat;
+import static java.nio.charset.StandardCharsets.UTF_16;
+import static org.junit.jupiter.api.Assertions.fail;
+
+import jakarta.xml.bind.JAXBException;
+import jakarta.xml.bind.Marshaller;
+import jakarta.xml.bind.Unmarshaller;
+import org.junit.jupiter.api.Test;
+
+class JaxbEncoderTest {
+ @Test
+ void compatibleMediaTypes() {
+ verifyThat(createEncoder())
+ .isCompatibleWith("application/xml")
+ .isCompatibleWith("text/xml")
+ .isCompatibleWith("application/*")
+ .isCompatibleWith("text/*")
+ .isCompatibleWith("*/*");
+ }
+
+ @Test
+ void incompatibleMediaTypes() {
+ verifyThat(createEncoder())
+ .isNotCompatibleWith("application/json")
+ .isNotCompatibleWith("text/html")
+ .isNotCompatibleWith("image/*");
+ }
+
+ @Test
+ void serialize() {
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .succeedsWith(
+ "");
+ }
+
+ @Test
+ void serializeWithUtf16() {
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .withMediaType("application/xml; charset=utf-16")
+ .succeedsWith(
+ "",
+ UTF_16);
+ }
+
+ @Test
+ void serializeList() {
+ verifyThat(createEncoder())
+ .converting(new PointList(new Point(1, 2), new Point(3, 4)))
+ .succeedsWith(
+ ""
+ + ""
+ + ""
+ + ""
+ + "");
+ }
+
+ @Test
+ void serializeWithFormattedXml() {
+ var customFactory =
+ new JaxbBindingFactory() {
+ private final JaxbBindingFactory delegate = JaxbBindingFactory.create();
+
+ /** Create a Marshaller with pretty printing. */
+ @Override
+ public Marshaller createMarshaller(Class> boundClass) throws JAXBException {
+ var marshaller = delegate.createMarshaller(boundClass);
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ return marshaller;
+ }
+
+ @Override
+ public Unmarshaller createUnmarshaller(Class> boundClass) {
+ return fail("unexpected unmarshalling");
+ }
+ };
+
+ verifyThat(createEncoder(customFactory))
+ .converting(new PointList(new Point(1, 2), new Point(3, 4)))
+ .succeedsWithNormalizingLineEndings(
+ "\n"
+ + "\n"
+ + " \n"
+ + " \n"
+ + "\n");
+ }
+
+ @Test
+ void serializeWithUnsupportedType() {
+ class NotAXmlRootElement {}
+
+ verifyThat(createEncoder()).converting(new NotAXmlRootElement()).isNotSupported();
+ }
+
+ @Test
+ void serializeWithUnsupportedMediaType() {
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .withMediaType("application/json")
+ .isNotSupported();
+ }
+
+ @Test
+ void mediaTypeIsAttached() {
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .withMediaType("application/xml")
+ .asBodyPublisher()
+ .hasMediaType("application/xml");
+
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .withMediaType("text/xml")
+ .asBodyPublisher()
+ .hasMediaType("text/xml");
+ }
+
+ @Test
+ void mediaTypeWithCharsetIsAttached() {
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .withMediaType("application/xml; charset=utf-16")
+ .asBodyPublisher()
+ .hasMediaType("application/xml; charset=utf-16");
+
+ verifyThat(createEncoder())
+ .converting(new Point(1, 2))
+ .withMediaType("text/xml; charset=utf-16")
+ .asBodyPublisher()
+ .hasMediaType("text/xml; charset=utf-16");
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/Point.java b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/Point.java
new file mode 100644
index 000000000..dd3c614f2
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/Point.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import jakarta.xml.bind.annotation.XmlAttribute;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import java.util.Objects;
+
+@XmlRootElement(name = "point")
+public class Point {
+ @XmlAttribute(required = true)
+ private final int x;
+
+ @XmlAttribute(required = true)
+ private final int y;
+
+ // Provide a no-arg constructor for JAXB
+ public Point() {
+ this(0, 0);
+ }
+
+ public Point(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof Point)) {
+ return false;
+ }
+ var other = (Point) obj;
+ return x == other.x && y == other.y;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(x, y);
+ }
+
+ @Override
+ public String toString() {
+ return "Point[" + x + ", " + y + "]";
+ }
+}
diff --git a/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/PointList.java b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/PointList.java
new file mode 100644
index 000000000..ed32628df
--- /dev/null
+++ b/methanol-jaxb-jakarta/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/jakarta/PointList.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2024 Moataz Abdelnasser
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package com.github.mizosoft.methanol.adapter.jaxb.jakarta;
+
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import java.util.ArrayList;
+import java.util.List;
+
+@XmlRootElement(name = "points")
+public class PointList {
+ @XmlElement(name = "point")
+ private final List points;
+
+ // Provide a no-arg constructor for JAXB
+ public PointList() {
+ this(new ArrayList<>());
+ }
+
+ public PointList(Point... points) {
+ this.points = List.of(points);
+ }
+
+ public PointList(List points) {
+ this.points = points;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PointList)) {
+ return false;
+ }
+ return points.equals(((PointList) obj).points);
+ }
+
+ @Override
+ public int hashCode() {
+ return points.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "PointList" + points.toString();
+ }
+}
diff --git a/methanol-jaxb/README.md b/methanol-jaxb/README.md
index c3f42e160..0621a54f7 100644
--- a/methanol-jaxb/README.md
+++ b/methanol-jaxb/README.md
@@ -1,6 +1,6 @@
# methanol-jaxb
-Adapters for XML using [JAXB][jaxb].
+Adapters for XML using Java EE's [JAXB][jaxb].
## Installation
diff --git a/methanol-jaxb/build.gradle.kts b/methanol-jaxb/build.gradle.kts
index 45f7ef4d3..6347e2647 100644
--- a/methanol-jaxb/build.gradle.kts
+++ b/methanol-jaxb/build.gradle.kts
@@ -11,5 +11,5 @@ dependencies {
api(libs.jaxb.api)
testImplementation(project(":methanol-testing"))
- testImplementation(libs.moxy)
+ testImplementation(libs.jaxb.impl)
}
diff --git a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactory.java b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactory.java
index d33b91507..72f6e3097 100644
--- a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactory.java
+++ b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -30,12 +30,9 @@
import javax.xml.bind.Unmarshaller;
final class CachingJaxbBindingFactory implements JaxbBindingFactory {
+ private final ConcurrentMap, JAXBContext> cachedContexts = new ConcurrentHashMap<>();
- private final ConcurrentMap, JAXBContext> cachedContexts;
-
- CachingJaxbBindingFactory() {
- cachedContexts = new ConcurrentHashMap<>();
- }
+ CachingJaxbBindingFactory() {}
@Override
public Marshaller createMarshaller(Class> boundClass) throws JAXBException {
@@ -47,7 +44,7 @@ public Unmarshaller createUnmarshaller(Class> boundClass) throws JAXBException
return getOrCreateContext(boundClass).createUnmarshaller();
}
- // not private for testing
+ // Visible for testing.
JAXBContext getOrCreateContext(Class> boundClass) {
return cachedContexts.computeIfAbsent(
boundClass,
diff --git a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapter.java b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapter.java
index c6e183e06..7c1717840 100644
--- a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapter.java
+++ b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapter.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -47,7 +47,6 @@
import org.checkerframework.checker.nullness.qual.Nullable;
abstract class JaxbAdapter extends AbstractBodyAdapter {
-
final JaxbBindingFactory jaxbFactory;
JaxbAdapter(JaxbBindingFactory jaxbFactory) {
@@ -57,17 +56,17 @@ abstract class JaxbAdapter extends AbstractBodyAdapter {
@Override
public boolean supportsType(TypeRef> type) {
- if (type.type() instanceof Class>) {
- Class> clazz = type.rawType();
- return clazz.isAnnotationPresent(XmlRootElement.class)
- || clazz.isAnnotationPresent(XmlType.class)
- || clazz.isAnnotationPresent(XmlEnum.class);
+ if (!(type.type() instanceof Class>)) {
+ return false;
}
- return false;
+
+ var clazz = type.rawType();
+ return clazz.isAnnotationPresent(XmlRootElement.class)
+ || clazz.isAnnotationPresent(XmlType.class)
+ || clazz.isAnnotationPresent(XmlEnum.class);
}
static final class Encoder extends JaxbAdapter implements BodyAdapter.Encoder {
-
Encoder(JaxbBindingFactory factory) {
super(factory);
}
@@ -77,9 +76,9 @@ public BodyPublisher toBody(Object object, @Nullable MediaType mediaType) {
requireNonNull(object);
requireSupport(object.getClass());
requireCompatibleOrNull(mediaType);
- ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();
+ var outputBuffer = new ByteArrayOutputStream();
try {
- Marshaller marshaller = jaxbFactory.createMarshaller(object.getClass());
+ var marshaller = jaxbFactory.createMarshaller(object.getClass());
String encoding;
if (mediaType != null && (encoding = mediaType.parameters().get("charset")) != null) {
marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding);
@@ -93,7 +92,6 @@ public BodyPublisher toBody(Object object, @Nullable MediaType mediaType) {
}
static final class Decoder extends JaxbAdapter implements BodyAdapter.Decoder {
-
Decoder(JaxbBindingFactory factory) {
super(factory);
}
@@ -103,9 +101,9 @@ public BodySubscriber toObject(TypeRef objectType, @Nullable MediaType
requireNonNull(objectType);
requireSupport(objectType);
requireCompatibleOrNull(mediaType);
- Class elementClass = objectType.exactRawType();
- Charset charset = charsetOrNull(mediaType);
- Unmarshaller unmarshaller = createUnmarshallerUnchecked(elementClass);
+ var elementClass = objectType.exactRawType();
+ var charset = charsetOrNull(mediaType);
+ var unmarshaller = createUnmarshallerUnchecked(elementClass);
return BodySubscribers.mapping(
BodySubscribers.ofByteArray(),
bytes ->
@@ -118,9 +116,9 @@ public BodySubscriber> toDeferredObject(
requireNonNull(objectType);
requireSupport(objectType);
requireCompatibleOrNull(mediaType);
- Class elementClass = objectType.exactRawType();
- Charset charset = charsetOrNull(mediaType);
- Unmarshaller unmarshaller = createUnmarshallerUnchecked(elementClass);
+ var elementClass = objectType.exactRawType();
+ var charset = charsetOrNull(mediaType);
+ var unmarshaller = createUnmarshallerUnchecked(elementClass);
return BodySubscribers.mapping(
BodySubscribers.ofInputStream(),
in -> () -> unmarshalValue(elementClass, unmarshaller, in, charset));
@@ -133,7 +131,7 @@ private T unmarshalValue(
@Nullable Charset charset) {
try {
// If the charset is known from the media type, use it for a Reader
- // to avoid the overhead of having to infer it from the document
+ // to avoid the overhead of having to infer it from the document.
return elementClass.cast(
charset != null
? unmarshaller.unmarshal(new InputStreamReader(in, charset))
diff --git a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapterFactory.java b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapterFactory.java
index 5dd089e5f..4137a18c9 100644
--- a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapterFactory.java
+++ b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbAdapterFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -27,7 +27,6 @@
/** Creates {@link com.github.mizosoft.methanol.BodyAdapter} implementations for XML using JAXB. */
public class JaxbAdapterFactory {
-
private JaxbAdapterFactory() {}
/** Returns a new {@code Encoder} using the default caching factory. */
diff --git a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbBindingFactory.java b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbBindingFactory.java
index 603b8e709..e2022cdaa 100644
--- a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbBindingFactory.java
+++ b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbBindingFactory.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -26,9 +26,10 @@
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
-/** Creates new {@link Marshaller} or {@link Unmarshaller} on demand for use by an adapter. */
+/**
+ * Creates new {@link Marshaller} or {@link Unmarshaller} objects on demand for use by an adapter.
+ */
public interface JaxbBindingFactory {
-
/** Returns a new {@code Marshaller} for encoding an object of the given class. */
Marshaller createMarshaller(Class> boundClass) throws JAXBException;
@@ -36,8 +37,8 @@ public interface JaxbBindingFactory {
Unmarshaller createUnmarshaller(Class> boundClass) throws JAXBException;
/**
- * Returns a default {@code JaxbBindingFactory} that creates and caches {@code JAXBContexts} for
- * each requested type.
+ * Returns a new {@code JaxbBindingFactory} that creates and caches {@code JAXBContexts} for each
+ * requested type.
*/
static JaxbBindingFactory create() {
return new CachingJaxbBindingFactory();
diff --git a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/UncheckedJaxbException.java b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/UncheckedJaxbException.java
index ed873e14d..09fa970f5 100644
--- a/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/UncheckedJaxbException.java
+++ b/methanol-jaxb/src/main/java/com/github/mizosoft/methanol/adapter/jaxb/UncheckedJaxbException.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -26,7 +26,6 @@
/** Unchecked wrapper over a {@link JAXBException}. */
public class UncheckedJaxbException extends RuntimeException {
-
/** Creates a new {@code UncheckedJaxbException} with the given cause. */
public UncheckedJaxbException(JAXBException cause) {
super(cause);
diff --git a/methanol-jaxb/src/main/java/module-info.java b/methanol-jaxb/src/main/java/module-info.java
index 538a4beae..a5183f12c 100644
--- a/methanol-jaxb/src/main/java/module-info.java
+++ b/methanol-jaxb/src/main/java/module-info.java
@@ -1,31 +1,11 @@
-/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
+import com.github.mizosoft.methanol.BodyAdapter;
+import com.github.mizosoft.methanol.adapter.jaxb.JaxbAdapterFactory;
/**
- * Provides {@link com.github.mizosoft.methanol.BodyAdapter.Encoder} and {@link
- * com.github.mizosoft.methanol.BodyAdapter.Decoder} implementations for XML using JAXB. Note that,
- * for the sake of configurability, the adapters are not service-provided by default. You will need
- * to explicitly declare service-providers that delegate to the instances created by {@link
- * com.github.mizosoft.methanol.adapter.jaxb.JaxbAdapterFactory}.
+ * Provides {@link BodyAdapter.Encoder} and {@link BodyAdapter.Decoder} implementations for XML
+ * using JAXB. Note that, for the sake of configurability, the adapters are not service-provided by
+ * default. You will need to explicitly declare service-providers that delegate to the instances
+ * created by {@link JaxbAdapterFactory}.
*/
module methanol.adapter.jaxb {
requires transitive methanol;
diff --git a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactoryTest.java b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactoryTest.java
index 0d6cde958..018285d34 100644
--- a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactoryTest.java
+++ b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/CachingJaxbBindingFactoryTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2019, 2020 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -24,15 +24,9 @@
import static org.assertj.core.api.Assertions.assertThat;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class CachingJaxbBindingFactoryTest {
- @BeforeAll
- static void registerJaxbImplementation() {
- JaxbUtils.registerImplementation();
- }
-
@Test
void cachesContextsForSameType() {
var factory = new CachingJaxbBindingFactory();
diff --git a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDecoderTest.java b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDecoderTest.java
index fac95da9f..2e29bd8c7 100644
--- a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDecoderTest.java
+++ b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDecoderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -27,15 +27,9 @@
import static java.nio.charset.StandardCharsets.UTF_16;
import com.github.mizosoft.methanol.testing.TestException;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class JaxbDecoderTest {
- @BeforeAll
- static void registerJaxbImplementation() {
- JaxbUtils.registerImplementation();
- }
-
@Test
void compatibleMediaTypes() {
verifyThat(createDecoder())
diff --git a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDeferredDecoderTest.java b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDeferredDecoderTest.java
index dc2096dc6..e19b6a1bb 100644
--- a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDeferredDecoderTest.java
+++ b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbDeferredDecoderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -27,15 +27,9 @@
import static java.nio.charset.StandardCharsets.UTF_16;
import com.github.mizosoft.methanol.testing.TestException;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class JaxbDeferredDecoderTest {
- @BeforeAll
- static void registerJaxbImplementation() {
- JaxbUtils.registerImplementation();
- }
-
@Test
void deserialize() {
verifyThat(createDecoder())
diff --git a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbEncoderTest.java b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbEncoderTest.java
index 6b1a3f2eb..eb03d58cf 100644
--- a/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbEncoderTest.java
+++ b/methanol-jaxb/src/test/java/com/github/mizosoft/methanol/adapter/jaxb/JaxbEncoderTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2022 Moataz Abdelnasser
+ * Copyright (c) 2024 Moataz Abdelnasser
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -30,15 +30,9 @@
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
-import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
class JaxbEncoderTest {
- @BeforeAll
- static void registerJaxbImplementation() {
- JaxbUtils.registerImplementation();
- }
-
@Test
void compatibleMediaTypes() {
verifyThat(createEncoder())
@@ -61,7 +55,8 @@ void incompatibleMediaTypes() {
void serialize() {
verifyThat(createEncoder())
.converting(new Point(1, 2))
- .succeedsWith("");
+ .succeedsWith(
+ "");
}
@Test
@@ -70,8 +65,8 @@ void serializeWithUtf16() {
.converting(new Point(1, 2))
.withMediaType("application/xml; charset=utf-16")
.succeedsWith(
- "" // MediaType lower-cases the charset
- + "", UTF_16);
+ "",
+ UTF_16);
}
@Test
@@ -79,47 +74,48 @@ void serializeList() {
verifyThat(createEncoder())
.converting(new PointList(new Point(1, 2), new Point(3, 4)))
.succeedsWith(
- ""
- + ""
- + ""
- + ""
- + "");
+ ""
+ + ""
+ + ""
+ + ""
+ + "");
}
@Test
- void serializeWithFormatedXml() {
- var customFactory = new JaxbBindingFactory() {
- private final JaxbBindingFactory delegate = JaxbBindingFactory.create();
-
- /** Create a Marshaller with pretty printing. */
- @Override public Marshaller createMarshaller(Class> boundClass) throws JAXBException {
- var marshaller = delegate.createMarshaller(boundClass);
- marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
- return marshaller;
- }
-
- @Override public Unmarshaller createUnmarshaller(Class> boundClass) {
- return fail("unexpected unmarshalling");
- }
- };
+ void serializeWithFormattedXml() {
+ var customFactory =
+ new JaxbBindingFactory() {
+ private final JaxbBindingFactory delegate = JaxbBindingFactory.create();
+
+ /** Create a Marshaller with pretty printing. */
+ @Override
+ public Marshaller createMarshaller(Class> boundClass) throws JAXBException {
+ var marshaller = delegate.createMarshaller(boundClass);
+ marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
+ return marshaller;
+ }
+
+ @Override
+ public Unmarshaller createUnmarshaller(Class> boundClass) {
+ return fail("unexpected unmarshalling");
+ }
+ };
verifyThat(createEncoder(customFactory))
.converting(new PointList(new Point(1, 2), new Point(3, 4)))
.succeedsWithNormalizingLineEndings(
- "\n"
- + "\n"
- + " \n"
- + " \n"
- + "\n");
+ "\n"
+ + "\n"
+ + " \n"
+ + " \n"
+ + "\n");
}
@Test
void serializeWithUnsupportedType() {
class NotAXmlRootElement {}
- verifyThat(createEncoder())
- .converting(new NotAXmlRootElement())
- .isNotSupported();
+ verifyThat(createEncoder()).converting(new NotAXmlRootElement()).isNotSupported();
}
@Test
diff --git a/settings.gradle.kts b/settings.gradle.kts
index a516314b0..966e95ee0 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,25 +1,3 @@
-/*
- * Copyright (c) 2023 Moataz Abdelnasser
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
rootProject.name = "methanol-parent"
include("methanol")
@@ -29,6 +7,7 @@ include("methanol-jackson")
include("methanol-jackson-flux")
include("methanol-protobuf")
include("methanol-jaxb")
+include("methanol-jaxb-jakarta")
include("methanol-brotli")
include("methanol-blackbox")
include("methanol-benchmarks")