From bbf14fefc50381165ac9407675f8fe7079dc5f02 Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Thu, 16 Apr 2026 09:49:49 -0400 Subject: [PATCH 1/5] Initial support for jackson module to control formatting of UUIDs --- .../crux/jackson/JavaUUIDDeserializer.java | 70 ++++++++++++++ .../fizzed/crux/jackson/JavaUUIDFormat.java | 14 +++ .../fizzed/crux/jackson/JavaUUIDModule.java | 18 ++++ .../crux/jackson/JavaUUIDSerializer.java | 57 +++++++++++ .../fizzed/crux/jackson/JavaUUIDStyle.java | 8 ++ .../crux/jackson/JavaUUIDModuleTest.java | 96 +++++++++++++++++++ 6 files changed, 263 insertions(+) create mode 100644 crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDDeserializer.java create mode 100644 crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDFormat.java create mode 100644 crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDModule.java create mode 100644 crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDSerializer.java create mode 100644 crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDStyle.java create mode 100644 crux-jackson/src/test/java/com/fizzed/crux/jackson/JavaUUIDModuleTest.java diff --git a/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDDeserializer.java b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDDeserializer.java new file mode 100644 index 0000000..0260a40 --- /dev/null +++ b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDDeserializer.java @@ -0,0 +1,70 @@ +package com.fizzed.crux.jackson; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.deser.ContextualDeserializer; + +import java.io.IOException; +import java.util.UUID; + +public class JavaUUIDDeserializer extends JsonDeserializer implements ContextualDeserializer { + + private final JavaUUIDStyle style; + + public JavaUUIDDeserializer() { + this(JavaUUIDStyle.DEFAULT); + } + + public JavaUUIDDeserializer(JavaUUIDStyle style) { + this.style = style; + } + + @Override + public UUID deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { + final String value = p.getValueAsString(); + + if (value == null || value.trim().isEmpty()) { + return null; + } + + // Re-insert hyphens to satisfy the standard UUID format + final String uuidStr; + + if (style == JavaUUIDStyle.DEFAULT) { + uuidStr = value; + } else if (style == JavaUUIDStyle.STRIPPED) { + if (value.length() != 32) { + throw new IllegalArgumentException("MD5 hex string must be exactly 32 characters long"); + } + + uuidStr = value.substring(0, 8) + "-" + + value.substring(8, 12) + "-" + + value.substring(12, 16) + "-" + + value.substring(16, 20) + "-" + + value.substring(20, 32); + } else { + throw new IllegalArgumentException("Unknown UUID style: " + style); + } + + try { + return UUID.fromString(uuidStr); + } catch (IllegalArgumentException e) { + return (UUID) ctxt.handleWeirdStringValue(UUID.class, value, "Invalid UUID format"); + } + } + + @Override + public JsonDeserializer createContextual(DeserializationContext ctxt, BeanProperty property) { + if (property != null) { + final JavaUUIDFormat format = property.getAnnotation(JavaUUIDFormat.class); + if (format != null) { + // Return a version of the deserializer tailored to this field's annotation + return new JavaUUIDDeserializer(format.value()); + } + } + return this; // Return default if no annotation is found + } + +} \ No newline at end of file diff --git a/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDFormat.java b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDFormat.java new file mode 100644 index 0000000..1b875b1 --- /dev/null +++ b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDFormat.java @@ -0,0 +1,14 @@ +package com.fizzed.crux.jackson; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface JavaUUIDFormat { + + JavaUUIDStyle value(); + +} \ No newline at end of file diff --git a/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDModule.java b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDModule.java new file mode 100644 index 0000000..38924c1 --- /dev/null +++ b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDModule.java @@ -0,0 +1,18 @@ +package com.fizzed.crux.jackson; + +import com.fasterxml.jackson.databind.module.SimpleModule; + +import java.util.UUID; + +public class JavaUUIDModule extends SimpleModule { + + public JavaUUIDModule() { + this(JavaUUIDStyle.DEFAULT); + } + + public JavaUUIDModule(JavaUUIDStyle defaultStyle) { + this.addSerializer(UUID.class, new JavaUUIDSerializer(defaultStyle)); + this.addDeserializer(UUID.class, new JavaUUIDDeserializer(defaultStyle)); + } + +} \ No newline at end of file diff --git a/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDSerializer.java b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDSerializer.java new file mode 100644 index 0000000..af4101b --- /dev/null +++ b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDSerializer.java @@ -0,0 +1,57 @@ +package com.fizzed.crux.jackson; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.ContextualSerializer; + +import java.io.IOException; +import java.util.UUID; + +public class JavaUUIDSerializer extends JsonSerializer implements ContextualSerializer { + + private final JavaUUIDStyle style; + + public JavaUUIDSerializer() { + this(JavaUUIDStyle.DEFAULT); + } + + public JavaUUIDSerializer(JavaUUIDStyle style) { + this.style = style; + } + + + @Override + public void serialize(UUID value, JsonGenerator gen, SerializerProvider serializers) throws IOException { + if (value == null) { + gen.writeNull(); + return; + } + + final String uuidStr = value.toString(); + + switch (this.style) { + case STRIPPED: + gen.writeString(uuidStr.replace("-", "")); + break; + case DEFAULT: + default: + gen.writeString(uuidStr); + break; + } + } + + @Override + public JsonSerializer createContextual(SerializerProvider prov, BeanProperty property) { + if (property != null) { + JavaUUIDFormat format = property.getAnnotation(JavaUUIDFormat.class); + if (format != null) { + // Return a new instance configured with the specific enum value + return new JavaUUIDSerializer(format.value()); + } + } + return this; // Use default if no annotation is present + } + +} \ No newline at end of file diff --git a/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDStyle.java b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDStyle.java new file mode 100644 index 0000000..b26aca6 --- /dev/null +++ b/crux-jackson/src/main/java/com/fizzed/crux/jackson/JavaUUIDStyle.java @@ -0,0 +1,8 @@ +package com.fizzed.crux.jackson; + +public enum JavaUUIDStyle { + + DEFAULT, + STRIPPED; + +} \ No newline at end of file diff --git a/crux-jackson/src/test/java/com/fizzed/crux/jackson/JavaUUIDModuleTest.java b/crux-jackson/src/test/java/com/fizzed/crux/jackson/JavaUUIDModuleTest.java new file mode 100644 index 0000000..c7ec1d7 --- /dev/null +++ b/crux-jackson/src/test/java/com/fizzed/crux/jackson/JavaUUIDModuleTest.java @@ -0,0 +1,96 @@ +package com.fizzed.crux.jackson; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; + +import java.util.UUID; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class JavaUUIDModuleTest { + + @Test + public void serializeDefaultStyle() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaUUIDModule()); + + final UUID uuid1 = UUID.fromString("185F18D6-DEBC-49dd-9f62-a8bf2831a868"); + + assertThat(objectMapper.writeValueAsString(uuid1), is("\"185f18d6-debc-49dd-9f62-a8bf2831a868\"")); + } + + @Test + public void serializeStrippedStyle() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaUUIDModule(JavaUUIDStyle.STRIPPED)); + + final UUID uuid1 = UUID.fromString("185f18d6-debc-49dd-9f62-a8bf2831a868"); + + assertThat(objectMapper.writeValueAsString(uuid1), is("\"185f18d6debc49dd9f62a8bf2831a868\"")); + } + + @Test + public void deserializeDefaultStyle() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaUUIDModule()); + + final UUID uuid1 = UUID.fromString("185F18D6-DEBC-49dd-9f62-a8bf2831a868"); + + assertThat(objectMapper.readValue("\"185f18d6-debc-49dd-9f62-a8bf2831a868\"", UUID.class), is(uuid1)); + } + + @Test + public void deserializeStrippedStyle() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaUUIDModule(JavaUUIDStyle.STRIPPED)); + + final UUID uuid1 = UUID.fromString("185F18D6-DEBC-49dd-9f62-a8bf2831a868"); + + // fails if not 32 chars long + try { + assertThat(objectMapper.readValue("\"185f18d6-debc-49dd-9f62-a8bf2831a868\"", UUID.class), is(uuid1)); + } catch (IllegalArgumentException e) { + // expected + } + + assertThat(objectMapper.readValue("\"185f18d6debc49dd9f62a8bf2831a868\"", UUID.class), is(uuid1)); + assertThat(objectMapper.readValue("\"185f18D6deBC49dd9f62a8bf2831a868\"", UUID.class), is(uuid1)); + } + + static public class Widget { + @JavaUUIDFormat(JavaUUIDStyle.STRIPPED) + private UUID uuid; + + public UUID getUuid() { + return uuid; + } + + public Widget setUuid(UUID uuid) { + this.uuid = uuid; + return this; + } + } + + @Test + public void serializeContextualStyle() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaUUIDModule(JavaUUIDStyle.DEFAULT)); + + final Widget w = new Widget() + .setUuid(UUID.fromString("185f18d6-debc-49dd-9f62-a8bf2831a868")); + + assertThat(objectMapper.writeValueAsString(w), is("{\"uuid\":\"185f18d6debc49dd9f62a8bf2831a868\"}")); + } + + @Test + public void deserializeContextualStyle() throws Exception { + final ObjectMapper objectMapper = new ObjectMapper() + .registerModule(new JavaUUIDModule(JavaUUIDStyle.DEFAULT)); + + final Widget w = objectMapper.readValue("{\"uuid\":\"185f18d6debc49dd9f62a8bf2831a868\"}", Widget.class); + + assertThat(w.getUuid(), is(UUID.fromString("185f18d6-debc-49dd-9f62-a8bf2831a868"))); + } + +} \ No newline at end of file From be2a480ff1d73e768c0c906e9f702d2413fbbbea Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Thu, 16 Apr 2026 15:37:55 -0400 Subject: [PATCH 2/5] Update parent maven --- crux-mediatype/pom.xml | 1 - pom.xml | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crux-mediatype/pom.xml b/crux-mediatype/pom.xml index e2e63b6..c608152 100644 --- a/crux-mediatype/pom.xml +++ b/crux-mediatype/pom.xml @@ -1,7 +1,6 @@ 4.0.0 - com.fizzed crux-mediatype crux-mediatype jar diff --git a/pom.xml b/pom.xml index d5f01d3..3a29ff8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,7 +1,6 @@ 4.0.0 - com.fizzed crux 1.0.49-SNAPSHOT pom @@ -9,7 +8,7 @@ com.fizzed maven-parent - 2.6.0 + 3.4.0 @@ -74,7 +73,7 @@ com.fizzed - crux-mime + crux-mediatype ${project.version} From 9a81a3269b691b49ebe107009fbecb52f9d7db99 Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Fri, 17 Apr 2026 08:43:49 -0400 Subject: [PATCH 3/5] Fix extra newline added to BASIC level okhttp logging --- .../src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java b/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java index d6d4f2f..007c393 100644 --- a/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java +++ b/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java @@ -76,9 +76,10 @@ public void logRequest( } //Slf4jUtil.log(messageLevel, logger, requestStartMessage); - sb.append("\n"); if (logHeaders) { + sb.append("\n"); + if (hasRequestBody) { // Request body headers are only present when installed as a network interceptor. Force // them to be included (when available) so there values are known. From 43d0876b84b136bd5afd75a1279160333a42eaaf Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Fri, 17 Apr 2026 08:51:18 -0400 Subject: [PATCH 4/5] Fix extra newline added to BASIC level okhttp response logging --- .../src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java b/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java index 007c393..c25d2e1 100644 --- a/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java +++ b/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java @@ -170,9 +170,11 @@ public void logResponse( .append(response.request().url()) .append(" (").append(tookMs).append("ms") .append(!logHeaders ? ", " + bodySize + " body" : "") - .append(')').append("\n"); + .append(')'); if (logHeaders) { + sb.append("\n"); + final Headers headers = response.headers(); for (int i = 0, count = headers.size(); i < count; i++) { //logHeader(messageLevel, logger, headers, i); From 9f7265537b19f5040d713ee907820c9d0b2d87a1 Mon Sep 17 00:00:00 2001 From: Joe Lauer Date: Fri, 17 Apr 2026 10:23:24 -0400 Subject: [PATCH 5/5] Trim any extra newlines from okhttp logging --- .../com/fizzed/crux/okhttp/OkHttpLogger.java | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java b/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java index c25d2e1..52d4ad2 100644 --- a/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java +++ b/crux-okhttp/src/main/java/com/fizzed/crux/okhttp/OkHttpLogger.java @@ -147,7 +147,12 @@ public void logRequest( } } } - + + // before we log anything, we need to strip any trailing newlines + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') { + sb.setLength(sb.length() - 1); + } + Slf4jUtil.log(messageLevel, logger, "{}", sb); } @@ -174,7 +179,7 @@ public void logResponse( if (logHeaders) { sb.append("\n"); - + final Headers headers = response.headers(); for (int i = 0, count = headers.size(); i < count; i++) { //logHeader(messageLevel, logger, headers, i); @@ -248,7 +253,12 @@ public void logResponse( } } } - + + // before we log anything, we need to strip any trailing newlines + while (sb.length() > 0 && sb.charAt(sb.length() - 1) == '\n') { + sb.setLength(sb.length() - 1); + } + Slf4jUtil.log(messageLevel, logger, "{}", sb); }