diff --git a/sdks/java/build.gradle.kts b/sdks/java/build.gradle.kts index 986e4190..307193ed 100644 --- a/sdks/java/build.gradle.kts +++ b/sdks/java/build.gradle.kts @@ -86,6 +86,11 @@ dependencies { api("com.fasterxml.jackson.core:jackson-databind:2.17.2") implementation("info.picocli:picocli:4.7.6") + // Optional: the MessagePack serializer. Compiled against, not bundled — a + // consumer that uses MsgpackSerializer adds this dependency themselves. + compileOnly("org.msgpack:jackson-dataformat-msgpack:0.9.8") + testImplementation("org.msgpack:jackson-dataformat-msgpack:0.9.8") + // Run the @TaskHandler processor over the tests so the generated companions // are exercised end-to-end. Consumers wire it the same way. testAnnotationProcessor(project(":processor")) diff --git a/sdks/java/src/main/java/org/byteveda/taskito/serialization/MsgpackSerializer.java b/sdks/java/src/main/java/org/byteveda/taskito/serialization/MsgpackSerializer.java new file mode 100644 index 00000000..4e0c89d9 --- /dev/null +++ b/sdks/java/src/main/java/org/byteveda/taskito/serialization/MsgpackSerializer.java @@ -0,0 +1,52 @@ +package org.byteveda.taskito.serialization; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.lang.reflect.Type; +import org.byteveda.taskito.errors.SerializationException; +import org.msgpack.jackson.dataformat.MessagePackFactory; + +/** + * A compact binary {@link Serializer} using MessagePack (via Jackson). + * + *

Optional: the {@code org.msgpack:jackson-dataformat-msgpack} dependency is + * {@code compileOnly}, so a consumer that selects this serializer must add it to + * their own build. JSON ({@link JsonSerializer}) remains the default. + */ +public final class MsgpackSerializer implements Serializer { + private final ObjectMapper mapper; + + public MsgpackSerializer() { + this(new ObjectMapper(new MessagePackFactory())); + } + + public MsgpackSerializer(ObjectMapper mapper) { + this.mapper = mapper; + } + + @Override + public byte[] serialize(Object value) { + try { + return mapper.writeValueAsBytes(value); + } catch (Exception e) { + throw new SerializationException("failed to serialize payload to MessagePack", e); + } + } + + @Override + public T deserialize(byte[] bytes, Class type) { + try { + return mapper.readValue(bytes, type); + } catch (Exception e) { + throw new SerializationException("failed to deserialize MessagePack payload", e); + } + } + + @Override + public Object deserialize(byte[] bytes, Type type) { + try { + return mapper.readValue(bytes, mapper.getTypeFactory().constructType(type)); + } catch (Exception e) { + throw new SerializationException("failed to deserialize MessagePack payload", e); + } + } +} diff --git a/sdks/java/src/test/java/org/byteveda/taskito/MsgpackSerializerTest.java b/sdks/java/src/test/java/org/byteveda/taskito/MsgpackSerializerTest.java new file mode 100644 index 00000000..63972a8e --- /dev/null +++ b/sdks/java/src/test/java/org/byteveda/taskito/MsgpackSerializerTest.java @@ -0,0 +1,34 @@ +package org.byteveda.taskito; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.LinkedHashMap; +import java.util.Map; +import org.byteveda.taskito.serialization.MsgpackSerializer; +import org.byteveda.taskito.serialization.Serializer; +import org.junit.jupiter.api.Test; + +class MsgpackSerializerTest { + + @Test + void roundTripsScalar() { + Serializer msgpack = new MsgpackSerializer(); + byte[] bytes = msgpack.serialize(42); + assertEquals(42, msgpack.deserialize(bytes, Integer.class)); + } + + @Test + @SuppressWarnings("unchecked") + void roundTripsMap() { + Serializer msgpack = new MsgpackSerializer(); + Map value = new LinkedHashMap<>(); + value.put("count", 3); + value.put("name", "taskito"); + + byte[] bytes = msgpack.serialize(value); + Map back = msgpack.deserialize(bytes, Map.class); + + assertEquals(3, ((Number) back.get("count")).intValue()); + assertEquals("taskito", back.get("name")); + } +}