From ba6d6d66fbdc2dc67d2a1bce71e30279440cf5c3 Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Mon, 15 Dec 2025 23:42:12 -0500 Subject: [PATCH 1/2] jackson 3 --- .../java/io/avaje/http/api/package-info.java | 2 +- .../{src/main/java => }/module-info.java | 2 + http-client/pom.xml | 26 +++ .../avaje/http/client/DHttpClientBuilder.java | 8 + .../http/client/Jackson3BodyAdapter.java | 177 ++++++++++++++++++ 5 files changed, 214 insertions(+), 1 deletion(-) rename http-client/{src/main/java => }/module-info.java (95%) create mode 100644 http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java diff --git a/http-api/src/main/java/io/avaje/http/api/package-info.java b/http-api/src/main/java/io/avaje/http/api/package-info.java index 4f502b35a..edb6ebecd 100644 --- a/http-api/src/main/java/io/avaje/http/api/package-info.java +++ b/http-api/src/main/java/io/avaje/http/api/package-info.java @@ -1,5 +1,5 @@ /** * Provides annotations to support Controllers for web frameworks that - * are route based (like Sparkjava, Javlin etc). + * are route based (like Avaje Jex, Javalin, Helidon, etc). */ package io.avaje.http.api; diff --git a/http-client/src/main/java/module-info.java b/http-client/module-info.java similarity index 95% rename from http-client/src/main/java/module-info.java rename to http-client/module-info.java index 25ba37d5e..9a3164ec4 100644 --- a/http-client/src/main/java/module-info.java +++ b/http-client/module-info.java @@ -28,9 +28,11 @@ requires static com.fasterxml.jackson.databind; requires static com.fasterxml.jackson.annotation; requires static com.fasterxml.jackson.core; + requires static tools.jackson.databind; requires static io.avaje.jsonb; requires static io.avaje.inject; requires static jdk.httpserver; exports io.avaje.http.client; + } diff --git a/http-client/pom.xml b/http-client/pom.xml index 9e5f4f0e9..7ec72c489 100644 --- a/http-client/pom.xml +++ b/http-client/pom.xml @@ -29,6 +29,13 @@ 1.2 + + tools.jackson.core + jackson-databind + 3.0.3 + true + + com.fasterxml.jackson.core jackson-databind @@ -155,6 +162,25 @@ + + org.moditect + moditect-maven-plugin + + + add-module-infos + package + + add-module-info + + + true + + module-info.java + + + + + diff --git a/http-client/src/main/java/io/avaje/http/client/DHttpClientBuilder.java b/http-client/src/main/java/io/avaje/http/client/DHttpClientBuilder.java index 1dd2b2f80..8bb2f3cda 100644 --- a/http-client/src/main/java/io/avaje/http/client/DHttpClientBuilder.java +++ b/http-client/src/main/java/io/avaje/http/client/DHttpClientBuilder.java @@ -138,6 +138,9 @@ private BodyAdapter defaultBodyAdapter() { if (bootLayer.findModule("com.fasterxml.jackson.databind").isPresent()) { return new JacksonBodyAdapter(); } + if (bootLayer.findModule("tools.jackson.databind").isPresent()) { + return new Jackson3BodyAdapter(); + } return bodyAdapter; }) .orElseGet(() -> { @@ -148,6 +151,11 @@ private BodyAdapter defaultBodyAdapter() { } try { return new JacksonBodyAdapter(); + } catch (Throwable e) { + // I guess it don't exist + } + try { + return new Jackson3BodyAdapter(); } catch (Throwable e) { return bodyAdapter; } diff --git a/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java b/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java new file mode 100644 index 000000000..03cea6e62 --- /dev/null +++ b/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java @@ -0,0 +1,177 @@ +package io.avaje.http.client; + +import java.lang.reflect.Type; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import tools.jackson.databind.ObjectMapper; +import tools.jackson.databind.ObjectReader; +import tools.jackson.databind.ObjectWriter; +import tools.jackson.databind.type.CollectionType; + +/** + * Jackson 3.x BodyAdapter to read and write beans as JSON. + * + *
{@code
+ * HttpClient.builder()
+ *     .baseUrl(baseUrl)
+ *     .bodyAdapter(new Jackson3BodyAdapter())
+ *     .build();
+ *
+ * }
+ */ +public final class Jackson3BodyAdapter implements BodyAdapter { + + private final ObjectMapper mapper; + + private final ConcurrentHashMap> beanWriterCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> beanReaderCache = new ConcurrentHashMap<>(); + private final ConcurrentHashMap> listReaderCache = new ConcurrentHashMap<>(); + + /** Create passing the ObjectMapper to use. */ + public Jackson3BodyAdapter(ObjectMapper mapper) { + this.mapper = mapper; + } + + /** Create with a ObjectMapper that allows unknown properties and inclusion non empty. */ + public Jackson3BodyAdapter() { + this.mapper = new ObjectMapper(); + } + + @SuppressWarnings("unchecked") + @Override + public BodyWriter beanWriter(Class cls) { + return (BodyWriter) + beanWriterCache.computeIfAbsent( + cls, + aClass -> { + try { + return new JWriter<>(mapper.writerFor(cls)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader beanReader(Class cls) { + return (BodyReader) + beanReaderCache.computeIfAbsent( + cls, + aClass -> { + try { + return new JReader<>(mapper.readerFor(cls)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public BodyWriter beanWriter(Type cls) { + return (BodyWriter) + beanWriterCache.computeIfAbsent( + cls, + aClass -> { + try { + return new JWriter<>(mapper.writerFor(mapper.getTypeFactory().constructType(cls))); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader beanReader(Type cls) { + return (BodyReader) + beanReaderCache.computeIfAbsent( + cls, + aClass -> { + try { + return new JReader<>(mapper.readerFor(mapper.getTypeFactory().constructType(cls))); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader> listReader(Class cls) { + return (BodyReader>) + listReaderCache.computeIfAbsent( + cls, + aClass -> { + try { + final CollectionType collectionType = + mapper.getTypeFactory().constructCollectionType(List.class, cls); + final ObjectReader reader = mapper.readerFor(collectionType); + return new JReader<>(reader); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @SuppressWarnings("unchecked") + @Override + public BodyReader> listReader(Type type) { + return (BodyReader>) + listReaderCache.computeIfAbsent( + type, + aType -> { + try { + var javaType = mapper.getTypeFactory().constructType(aType); + final CollectionType collectionType = + mapper.getTypeFactory().constructCollectionType(List.class, javaType); + final ObjectReader reader = mapper.readerFor(collectionType); + return new JReader<>(reader); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + private static final class JReader implements BodyReader { + + private final ObjectReader reader; + + JReader(ObjectReader reader) { + this.reader = reader; + } + + @Override + public T readBody(String content) { + return reader.readValue(content); + } + + @Override + public T read(BodyContent bodyContent) { + return reader.readValue(bodyContent.content()); + } + } + + private static final class JWriter implements BodyWriter { + + private final ObjectWriter writer; + + public JWriter(ObjectWriter writer) { + this.writer = writer; + } + + @Override + public BodyContent write(T bean, String contentType) { + // ignoring the requested contentType and always + // writing the body as json content + return write(bean); + } + + @Override + public BodyContent write(T bean) { + return BodyContent.asJson(writer.writeValueAsBytes(bean)); + } + } +} From 40aae1204a784e87ed488c670f16ba6ae02ab890 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 16 Dec 2025 22:38:34 +1300 Subject: [PATCH 2/2] Format and remove extra public modifier --- .../http/client/Jackson3BodyAdapter.java | 118 ++++++++---------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java b/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java index 03cea6e62..20557cadc 100644 --- a/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java +++ b/http-client/src/main/java/io/avaje/http/client/Jackson3BodyAdapter.java @@ -41,98 +41,80 @@ public Jackson3BodyAdapter() { @SuppressWarnings("unchecked") @Override public BodyWriter beanWriter(Class cls) { - return (BodyWriter) - beanWriterCache.computeIfAbsent( - cls, - aClass -> { - try { - return new JWriter<>(mapper.writerFor(cls)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return (BodyWriter) beanWriterCache.computeIfAbsent(cls, aClass -> { + try { + return new JWriter<>(mapper.writerFor(cls)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @SuppressWarnings("unchecked") @Override public BodyReader beanReader(Class cls) { - return (BodyReader) - beanReaderCache.computeIfAbsent( - cls, - aClass -> { - try { - return new JReader<>(mapper.readerFor(cls)); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return (BodyReader) beanReaderCache.computeIfAbsent(cls, aClass -> { + try { + return new JReader<>(mapper.readerFor(cls)); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @SuppressWarnings("unchecked") @Override public BodyWriter beanWriter(Type cls) { - return (BodyWriter) - beanWriterCache.computeIfAbsent( - cls, - aClass -> { - try { - return new JWriter<>(mapper.writerFor(mapper.getTypeFactory().constructType(cls))); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return (BodyWriter) beanWriterCache.computeIfAbsent(cls, aClass -> { + try { + return new JWriter<>(mapper.writerFor(mapper.getTypeFactory().constructType(cls))); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @SuppressWarnings("unchecked") @Override public BodyReader beanReader(Type cls) { - return (BodyReader) - beanReaderCache.computeIfAbsent( - cls, - aClass -> { - try { - return new JReader<>(mapper.readerFor(mapper.getTypeFactory().constructType(cls))); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return (BodyReader) beanReaderCache.computeIfAbsent(cls, aClass -> { + try { + return new JReader<>(mapper.readerFor(mapper.getTypeFactory().constructType(cls))); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @SuppressWarnings("unchecked") @Override public BodyReader> listReader(Class cls) { - return (BodyReader>) - listReaderCache.computeIfAbsent( - cls, - aClass -> { - try { - final CollectionType collectionType = - mapper.getTypeFactory().constructCollectionType(List.class, cls); - final ObjectReader reader = mapper.readerFor(collectionType); - return new JReader<>(reader); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return (BodyReader>) listReaderCache.computeIfAbsent(cls, aClass -> { + try { + final CollectionType collectionType = + mapper.getTypeFactory().constructCollectionType(List.class, cls); + final ObjectReader reader = mapper.readerFor(collectionType); + return new JReader<>(reader); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } @SuppressWarnings("unchecked") @Override public BodyReader> listReader(Type type) { - return (BodyReader>) - listReaderCache.computeIfAbsent( - type, - aType -> { - try { - var javaType = mapper.getTypeFactory().constructType(aType); - final CollectionType collectionType = - mapper.getTypeFactory().constructCollectionType(List.class, javaType); - final ObjectReader reader = mapper.readerFor(collectionType); - return new JReader<>(reader); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + return (BodyReader>) listReaderCache.computeIfAbsent(type, aType -> { + try { + var javaType = mapper.getTypeFactory().constructType(aType); + final CollectionType collectionType = + mapper.getTypeFactory().constructCollectionType(List.class, javaType); + final ObjectReader reader = mapper.readerFor(collectionType); + return new JReader<>(reader); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); } private static final class JReader implements BodyReader { @@ -158,7 +140,7 @@ private static final class JWriter implements BodyWriter { private final ObjectWriter writer; - public JWriter(ObjectWriter writer) { + JWriter(ObjectWriter writer) { this.writer = writer; }