Introduce JsonCloudEventData (#251)

* Implemented JsonCloudEventData

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Suggestion

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
Francesco Guardiani 2020-10-29 15:59:15 +01:00 committed by GitHub
parent f9e31efaa3
commit 5e747e7278
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 111 additions and 37 deletions

View File

@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.databind.node.JsonNodeType;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventData;
import io.cloudevents.SpecVersion;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.data.BytesCloudEventData;
@ -81,7 +82,7 @@ public class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
}
}
byte[] data = null;
CloudEventData data = null;
// Now let's handle the data
switch (specVersion) {
@ -89,16 +90,16 @@ public class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
boolean isBase64 = "base64".equals(getOptionalStringNode(this.node, this.p, "datacontentencoding"));
if (node.has("data")) {
if (isBase64) {
data = node.remove("data").binaryValue();
data = new BytesCloudEventData(node.remove("data").binaryValue());
} else {
if (JsonFormat.dataIsJsonContentType(contentType)) {
// This solution is quite bad, but i see no alternatives now.
// Hopefully in future we can improve it
data = node.remove("data").toString().getBytes();
data = new JsonCloudEventData(node.remove("data"));
} else {
JsonNode dataNode = node.remove("data");
assertNodeType(dataNode, JsonNodeType.STRING, "data", "Because content type is not a json, only a string is accepted as data");
data = dataNode.asText().getBytes();
data = new BytesCloudEventData(dataNode.asText().getBytes());
}
}
}
@ -107,16 +108,16 @@ public class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
throw MismatchedInputException.from(p, CloudEvent.class, "CloudEvent cannot have both 'data' and 'data_base64' fields");
}
if (node.has("data_base64")) {
data = node.remove("data_base64").binaryValue();
data = new BytesCloudEventData(node.remove("data_base64").binaryValue());
} else if (node.has("data")) {
if (JsonFormat.dataIsJsonContentType(contentType)) {
// This solution is quite bad, but i see no alternatives now.
// Hopefully in future we can improve it
data = node.remove("data").toString().getBytes();
data = new JsonCloudEventData(node.remove("data"));
} else {
JsonNode dataNode = node.remove("data");
assertNodeType(dataNode, JsonNodeType.STRING, "data", "Because content type is not a json, only a string is accepted as data");
data = dataNode.asText().getBytes();
data = new BytesCloudEventData(dataNode.asText().getBytes());
}
}
}
@ -143,7 +144,7 @@ public class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
});
if (data != null) {
return visitor.end(new BytesCloudEventData(data));
return visitor.end(data);
}
return visitor.end();
} catch (IOException e) {

View File

@ -21,6 +21,7 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventData;
import io.cloudevents.core.impl.CloudEventUtils;
import io.cloudevents.rw.CloudEventAttributesWriter;
import io.cloudevents.rw.CloudEventExtensionsWriter;
@ -113,28 +114,33 @@ public class CloudEventSerializer extends StdSerializer<CloudEvent> {
// Serialize data
if (value.getData() != null) {
byte[] data = value.getData().toBytes();
String contentType = value.getDataContentType();
if (shouldSerializeBase64(contentType)) {
switch (value.getSpecVersion()) {
case V03:
gen.writeStringField("datacontentencoding", "base64");
gen.writeFieldName("data");
gen.writeBinary(data);
break;
case V1:
gen.writeFieldName("data_base64");
gen.writeBinary(data);
break;
}
} else if (JsonFormat.dataIsJsonContentType(contentType)) {
// TODO really bad b/c it allocates stuff, is there another solution out there?
char[] dataAsString = new String(data, StandardCharsets.UTF_8).toCharArray();
gen.writeFieldName("data");
gen.writeRawValue(dataAsString, 0, dataAsString.length);
CloudEventData data = value.getData();
if (data instanceof JsonCloudEventData) {
gen.writeObjectField("data", ((JsonCloudEventData) data).getNode());
} else {
gen.writeFieldName("data");
gen.writeUTF8String(data, 0, data.length);
byte[] dataBytes = data.toBytes();
String contentType = value.getDataContentType();
if (shouldSerializeBase64(contentType)) {
switch (value.getSpecVersion()) {
case V03:
gen.writeStringField("datacontentencoding", "base64");
gen.writeFieldName("data");
gen.writeBinary(dataBytes);
break;
case V1:
gen.writeFieldName("data_base64");
gen.writeBinary(dataBytes);
break;
}
} else if (JsonFormat.dataIsJsonContentType(contentType)) {
// TODO really bad b/c it allocates stuff, is there another solution out there?
char[] dataAsString = new String(dataBytes, StandardCharsets.UTF_8).toCharArray();
gen.writeFieldName("data");
gen.writeRawValue(dataAsString, 0, dataAsString.length);
} else {
gen.writeFieldName("data");
gen.writeUTF8String(dataBytes, 0, dataBytes.length);
}
}
}
gen.writeEndObject();

View File

@ -0,0 +1,48 @@
package io.cloudevents.jackson;
import com.fasterxml.jackson.databind.JsonNode;
import io.cloudevents.CloudEventData;
import java.util.Objects;
/**
* This class is a wrapper for Jackson {@link JsonNode} implementing the {@link CloudEventData}
*/
public class JsonCloudEventData implements CloudEventData {
private final JsonNode node;
public JsonCloudEventData(JsonNode node) {
Objects.requireNonNull(node);
this.node = node;
}
@Override
public byte[] toBytes() {
return node.toString().getBytes();
}
public JsonNode getNode() {
return node;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
JsonCloudEventData that = (JsonCloudEventData) o;
return Objects.equals(getNode(), that.getNode());
}
@Override
public int hashCode() {
return Objects.hash(getNode());
}
@Override
public String toString() {
return "JsonCloudEventData{" +
"node=" + node +
'}';
}
}

View File

@ -19,7 +19,10 @@ package io.cloudevents.jackson;
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.SpecVersion;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.provider.EventFormatProvider;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
@ -103,7 +106,7 @@ class JsonFormatTest {
assertThat(serialized).isNotEmpty();
CloudEvent output = getFormat().deserialize(serialized);
assertThat(output).isEqualTo(input);
assertThat(output).isEqualTo(normalizeToJsonValueIfNeeded(input));
}
public static Stream<Arguments> serializeTestArgumentsDefault() {
@ -151,8 +154,8 @@ class JsonFormatTest {
public static Stream<Arguments> deserializeTestArguments() {
return Stream.of(
Arguments.of("v03/min.json", V03_MIN),
Arguments.of("v03/json_data.json", V03_WITH_JSON_DATA),
Arguments.of("v03/json_data_with_ext.json", V03_WITH_JSON_DATA_WITH_EXT),
Arguments.of("v03/json_data.json", normalizeToJsonValueIfNeeded(V03_WITH_JSON_DATA)),
Arguments.of("v03/json_data_with_ext.json", normalizeToJsonValueIfNeeded(V03_WITH_JSON_DATA_WITH_EXT)),
Arguments.of("v03/base64_json_data.json", V03_WITH_JSON_DATA),
Arguments.of("v03/base64_json_data_with_ext.json", V03_WITH_JSON_DATA_WITH_EXT),
Arguments.of("v03/xml_data.json", V03_WITH_XML_DATA),
@ -160,8 +163,8 @@ class JsonFormatTest {
Arguments.of("v03/text_data.json", V03_WITH_TEXT_DATA),
Arguments.of("v03/base64_text_data.json", V03_WITH_TEXT_DATA),
Arguments.of("v1/min.json", V1_MIN),
Arguments.of("v1/json_data.json", V1_WITH_JSON_DATA),
Arguments.of("v1/json_data_with_ext.json", V1_WITH_JSON_DATA_WITH_EXT),
Arguments.of("v1/json_data.json", normalizeToJsonValueIfNeeded(V1_WITH_JSON_DATA)),
Arguments.of("v1/json_data_with_ext.json", normalizeToJsonValueIfNeeded(V1_WITH_JSON_DATA_WITH_EXT)),
Arguments.of("v1/base64_json_data.json", V1_WITH_JSON_DATA),
Arguments.of("v1/base64_json_data_with_ext.json", V1_WITH_JSON_DATA_WITH_EXT),
Arguments.of("v1/xml_data.json", V1_WITH_XML_DATA),
@ -185,7 +188,11 @@ class JsonFormatTest {
);
}
public static byte[] loadFile(String input) {
private JsonFormat getFormat() {
return (JsonFormat) EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
}
private static byte[] loadFile(String input) {
try {
return String.join(
"",
@ -196,8 +203,20 @@ class JsonFormatTest {
}
}
private JsonFormat getFormat() {
return (JsonFormat) EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
private static CloudEvent normalizeToJsonValueIfNeeded(CloudEvent event) {
if (event.getData() != null && JsonFormat.dataIsJsonContentType(event.getDataContentType())) {
CloudEventBuilder builder = null;
if (event.getSpecVersion() == SpecVersion.V1) {
builder = CloudEventBuilder.v1(event);
} else {
builder = CloudEventBuilder.v03(event);
}
return builder
.withData(new JsonCloudEventData(JsonNodeFactory.instance.objectNode()))
.build();
} else {
return event;
}
}
}