From c7baada60560c75ab6974b294a0f890d55482f04 Mon Sep 17 00:00:00 2001 From: Francesco Guardiani Date: Wed, 11 Nov 2020 08:54:01 +0100 Subject: [PATCH] Json mapper (#258) * Implemented Pojo mapper using jackson Added other exception kinds Signed-off-by: Francesco Guardiani * Fixup for the rebase Signed-off-by: Francesco Guardiani * Removed comment Signed-off-by: Francesco Guardiani * Equals and hash code Signed-off-by: Francesco Guardiani * Fixed rebase issues Signed-off-by: Francesco Guardiani --- .../cloudevents/rw/CloudEventRWException.java | 9 +++ .../jackson/PojoCloudEventData.java | 54 +++++++++++++++++ .../jackson/PojoCloudEventDataMapper.java | 48 +++++++++++++++ .../java/io/cloudevents/jackson/MyPojo.java | 38 ++++++++++++ .../jackson/PojoCloudEventDataMapperTest.java | 59 +++++++++++++++++++ 5 files changed, 208 insertions(+) create mode 100644 formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventData.java create mode 100644 formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventDataMapper.java create mode 100644 formats/json-jackson/src/test/java/io/cloudevents/jackson/MyPojo.java create mode 100644 formats/json-jackson/src/test/java/io/cloudevents/jackson/PojoCloudEventDataMapperTest.java diff --git a/api/src/main/java/io/cloudevents/rw/CloudEventRWException.java b/api/src/main/java/io/cloudevents/rw/CloudEventRWException.java index eae875e0..05f04ef3 100644 --- a/api/src/main/java/io/cloudevents/rw/CloudEventRWException.java +++ b/api/src/main/java/io/cloudevents/rw/CloudEventRWException.java @@ -25,6 +25,7 @@ public class CloudEventRWException extends RuntimeException { INVALID_ATTRIBUTE_TYPE, INVALID_ATTRIBUTE_VALUE, INVALID_EXTENSION_TYPE, + DATA_CONVERSION, OTHER } @@ -85,6 +86,14 @@ public class CloudEventRWException extends RuntimeException { ); } + public static CloudEventRWException newDataConversion(Throwable cause, String to) { + return new CloudEventRWException( + CloudEventRWExceptionKind.DATA_CONVERSION, + "Error while trying to convert data to " + to, + cause + ); + } + public static CloudEventRWException newOther(Throwable cause) { return new CloudEventRWException( CloudEventRWExceptionKind.OTHER, diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventData.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventData.java new file mode 100644 index 00000000..3a6777c1 --- /dev/null +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventData.java @@ -0,0 +1,54 @@ +package io.cloudevents.jackson; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEventData; +import io.cloudevents.rw.CloudEventRWException; + +import java.util.Objects; + +public class PojoCloudEventData implements CloudEventData { + + private final ObjectMapper mapper; + private byte[] memoizedValue; + private final T value; + + protected PojoCloudEventData(ObjectMapper mapper, T value) { + this(mapper, value, null); + } + + protected PojoCloudEventData(ObjectMapper mapper, T value, byte[] memoizedValue) { + this.mapper = mapper; + this.value = value; + this.memoizedValue = memoizedValue; + } + + public T getValue() { + return value; + } + + @Override + public byte[] toBytes() { + if (this.memoizedValue == null) { + try { + this.memoizedValue = mapper.writeValueAsBytes(value); + } catch (JsonProcessingException e) { + throw CloudEventRWException.newDataConversion(e, "byte[]"); + } + } + return this.memoizedValue; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PojoCloudEventData that = (PojoCloudEventData) o; + return Objects.equals(getValue(), that.getValue()); + } + + @Override + public int hashCode() { + return Objects.hash(getValue()); + } +} diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventDataMapper.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventDataMapper.java new file mode 100644 index 00000000..a805d189 --- /dev/null +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/PojoCloudEventDataMapper.java @@ -0,0 +1,48 @@ +package io.cloudevents.jackson; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.cloudevents.CloudEventData; +import io.cloudevents.rw.CloudEventDataMapper; +import io.cloudevents.rw.CloudEventRWException; + +public class PojoCloudEventDataMapper implements CloudEventDataMapper> { + + private final ObjectMapper mapper; + private final TypeReference target; + + private PojoCloudEventDataMapper(ObjectMapper mapper, TypeReference target) { + this.mapper = mapper; + this.target = target; + } + + @Override + public PojoCloudEventData map(CloudEventData data) throws CloudEventRWException { + // Best case, event is already from json + if (data instanceof JsonCloudEventData) { + JsonNode node = ((JsonCloudEventData) data).getNode(); + T value; + try { + value = this.mapper.convertValue(node, target); + } catch (Exception e) { + throw CloudEventRWException.newDataConversion(e, target.getType().toString()); + } + return new PojoCloudEventData<>(mapper, value); + } + + // Worst case, deserialize from bytes + T value; + byte[] bytes = data.toBytes(); + try { + value = this.mapper.readValue(bytes, this.target); + } catch (Exception e) { + throw CloudEventRWException.newDataConversion(e, target.getType().toString()); + } + return new PojoCloudEventData<>(mapper, value, bytes); + } + + public static PojoCloudEventDataMapper from(ObjectMapper mapper, TypeReference target) { + return new PojoCloudEventDataMapper<>(mapper, target); + } +} diff --git a/formats/json-jackson/src/test/java/io/cloudevents/jackson/MyPojo.java b/formats/json-jackson/src/test/java/io/cloudevents/jackson/MyPojo.java new file mode 100644 index 00000000..7e050a96 --- /dev/null +++ b/formats/json-jackson/src/test/java/io/cloudevents/jackson/MyPojo.java @@ -0,0 +1,38 @@ +package io.cloudevents.jackson; + +import java.util.Objects; + +public class MyPojo { + public int a; + public String b; + + public MyPojo() { + } + + public MyPojo(int a, String b) { + this.a = a; + this.b = b; + } + + public int getA() { + return a; + } + + public void setA(int a) { + this.a = a; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MyPojo myPojo = (MyPojo) o; + return getA() == myPojo.getA() && + Objects.equals(b, myPojo.b); + } + + @Override + public int hashCode() { + return Objects.hash(getA(), b); + } +} diff --git a/formats/json-jackson/src/test/java/io/cloudevents/jackson/PojoCloudEventDataMapperTest.java b/formats/json-jackson/src/test/java/io/cloudevents/jackson/PojoCloudEventDataMapperTest.java new file mode 100644 index 00000000..14cd5d13 --- /dev/null +++ b/formats/json-jackson/src/test/java/io/cloudevents/jackson/PojoCloudEventDataMapperTest.java @@ -0,0 +1,59 @@ +package io.cloudevents.jackson; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.impl.CloudEventUtils; +import io.cloudevents.core.test.Data; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PojoCloudEventDataMapperTest { + + private final JsonNode myPojoJson = JsonNodeFactory.instance.objectNode().put("a", 10).put("b", "Hello World!"); + private final String myPojoSerialized = myPojoJson.toString(); + private final MyPojo myPojo = new MyPojo(10, "Hello World!"); + + @Test + public void testWithBytes() { + ObjectMapper objectMapper = new ObjectMapper(); + + CloudEvent event = CloudEventBuilder.v1(Data.V1_MIN) + .withData("application/json", myPojoSerialized.getBytes()) + .build(); + + PojoCloudEventData mappedData = CloudEventUtils.mapData( + event, + PojoCloudEventDataMapper.from(objectMapper, new TypeReference() { + }) + ); + assertThat(mappedData) + .isNotNull() + .extracting(PojoCloudEventData::getValue) + .isEqualTo(myPojo); + } + + @Test + public void testWithJson() { + ObjectMapper objectMapper = new ObjectMapper(); + + CloudEvent event = CloudEventBuilder.v1(Data.V1_MIN) + .withData("application/json", new JsonCloudEventData(myPojoJson)) + .build(); + + PojoCloudEventData mappedData = CloudEventUtils.mapData( + event, + PojoCloudEventDataMapper.from(objectMapper, new TypeReference() { + }) + ); + assertThat(mappedData) + .isNotNull() + .extracting(PojoCloudEventData::getValue) + .isEqualTo(myPojo); + } + +}