Progress on implementing serialization

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
This commit is contained in:
slinkydeveloper 2020-04-20 21:01:10 +02:00 committed by Francesco Guardiani
parent 7dcfdba30d
commit 4df01cd279
11 changed files with 167 additions and 24 deletions

View File

@ -16,7 +16,6 @@ public final class EventFormatProvider {
private HashMap<String, EventFormat> formats;
//TODO register stuff with SPI
private EventFormatProvider() {
this.formats = new HashMap<>();

View File

@ -7,7 +7,7 @@ import io.cloudevents.message.*;
import java.util.*;
public final class CloudEventImpl implements CloudEvent, BinaryMessage {
public final class CloudEventImpl implements CloudEvent, BinaryMessage, BinaryMessageExtensions {
private final AttributesInternal attributes;
private final byte[] data;
@ -71,10 +71,7 @@ public final class CloudEventImpl implements CloudEvent, BinaryMessage {
}
@Override
public <T extends BinaryMessageVisitor<V>, V> V visit(BinaryMessageVisitorFactory<T, V> visitorFactory) throws MessageVisitException, IllegalStateException {
BinaryMessageVisitor<V> visitor = visitorFactory.createBinaryMessageVisitor(this.attributes.getSpecVersion());
this.attributes.visit(visitor);
public void visitExtensions(BinaryMessageExtensionsVisitor visitor) throws MessageVisitException {
// TODO to be improved
for (Map.Entry<String, Object> entry : this.extensions.entrySet()) {
if (entry.getValue() instanceof String) {
@ -88,6 +85,13 @@ public final class CloudEventImpl implements CloudEvent, BinaryMessage {
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
}
}
}
@Override
public <T extends BinaryMessageVisitor<V>, V> V visit(BinaryMessageVisitorFactory<T, V> visitorFactory) throws MessageVisitException, IllegalStateException {
BinaryMessageVisitor<V> visitor = visitorFactory.createBinaryMessageVisitor(this.attributes.getSpecVersion());
this.attributes.visitAttributes(visitor);
this.visitExtensions(visitor);
if (this.data != null) {
visitor.setBody(this.data);

View File

@ -3,10 +3,9 @@ package io.cloudevents.message;
public interface BinaryMessageAttributes {
/**
*
* @param visitor
* @throws MessageVisitException
*/
void visit(BinaryMessageAttributesVisitor visitor) throws MessageVisitException;
void visitAttributes(BinaryMessageAttributesVisitor visitor) throws MessageVisitException;
}

View File

@ -5,6 +5,7 @@ import io.cloudevents.types.Time;
import java.net.URI;
import java.time.ZonedDateTime;
@FunctionalInterface
public interface BinaryMessageAttributesVisitor {
void setAttribute(String name, String value) throws MessageVisitException;

View File

@ -0,0 +1,11 @@
package io.cloudevents.message;
public interface BinaryMessageExtensions {
/**
* @param visitor
* @throws MessageVisitException
*/
void visitExtensions(BinaryMessageExtensionsVisitor visitor) throws MessageVisitException;
}

View File

@ -1,5 +1,6 @@
package io.cloudevents.message;
@FunctionalInterface
public interface BinaryMessageExtensionsVisitor {
void setExtension(String name, String value) throws MessageVisitException;

View File

@ -93,7 +93,7 @@ public final class AttributesImpl implements AttributesInternal {
}
@Override
public void visit(BinaryMessageAttributesVisitor visitor) throws MessageVisitException {
public void visitAttributes(BinaryMessageAttributesVisitor visitor) throws MessageVisitException {
visitor.setAttribute(
ContextAttributes.ID.name().toLowerCase(),
this.id

View File

@ -108,7 +108,7 @@ public final class AttributesImpl implements AttributesInternal {
}
@Override
public void visit(BinaryMessageAttributesVisitor visitor) throws MessageVisitException {
public void visitAttributes(BinaryMessageAttributesVisitor visitor) throws MessageVisitException {
visitor.setAttribute(
ContextAttributes.ID.name().toLowerCase(),
this.id

View File

@ -1,23 +1,128 @@
package io.cloudevents.format.json;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.AttributesInternal;
import io.cloudevents.impl.CloudEventImpl;
import io.cloudevents.message.BinaryMessageAttributesVisitor;
import io.cloudevents.message.BinaryMessageExtensionsVisitor;
import io.cloudevents.message.MessageVisitException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class CloudEventSerializer extends StdSerializer<CloudEvent> {
protected CloudEventSerializer() {
super(CloudEvent.class);
}
private static class AttributesSerializer implements BinaryMessageAttributesVisitor {
private JsonGenerator gen;
public AttributesSerializer(JsonGenerator gen) {
this.gen = gen;
}
@Override
public void setAttribute(String name, String value) throws MessageVisitException {
try {
gen.writeStringField(name, value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
private static class ExtensionsSerializer implements BinaryMessageExtensionsVisitor {
private JsonGenerator gen;
private SerializerProvider provider;
public ExtensionsSerializer(JsonGenerator gen, SerializerProvider provider) {
this.gen = gen;
this.provider = provider;
}
@Override
public void setExtension(String name, String value) throws MessageVisitException {
try {
gen.writeStringField(name, value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setExtension(String name, Number value) throws MessageVisitException {
try {
gen.writeFieldName(name);
provider.findValueSerializer(value.getClass()).serialize(value, gen, provider);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void setExtension(String name, Boolean value) throws MessageVisitException {
try {
gen.writeBooleanField(name, value);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@Override
public void serialize(CloudEvent value, JsonGenerator gen, SerializerProvider provider) throws IOException {
JsonSerializer<Object> attributesSerializer = provider.findValueSerializer(value.getAttributes().getClass());
// Serialize attributes
attributesSerializer.serialize(value.getAttributes(), gen, provider);
gen.writeStartObject();
gen.writeStringField("specversion", value.getAttributes().getSpecVersion().toString());
// Serialize attributes
AttributesInternal attributesInternal = (AttributesInternal) value.getAttributes();
try {
attributesInternal.visitAttributes(new AttributesSerializer(gen));
} catch (RuntimeException e) {
throw (IOException) e.getCause();
}
// Serialize extensions
try {
((CloudEventImpl) value).visitExtensions(new ExtensionsSerializer(gen, provider));
} catch (RuntimeException e) {
throw (IOException) e.getCause();
}
// Serialize data
Optional<byte[]> dataOptional = value.getData();
String contentType = attributesInternal.getDataContentType().orElse(null);
if (dataOptional.isPresent()) {
if (JsonFormat.shouldSerializeBase64(contentType)) {
switch (attributesInternal.getSpecVersion()) {
case V03:
gen.writeStringField("datacontentencoding", "base64");
gen.writeFieldName("data");
gen.writeBinary(dataOptional.get());
break;
case V1:
gen.writeFieldName("data_base64");
gen.writeBinary(dataOptional.get());
break;
}
} else if (JsonFormat.isJsonContentType(contentType)) {
// TODO really bad, is there another solution out there?
char[] data = new String(dataOptional.get(), StandardCharsets.UTF_8).toCharArray();
gen.writeFieldName("data");
gen.writeRawValue(data, 0, data.length);
} else {
byte[] data = dataOptional.get();
gen.writeFieldName("data");
gen.writeUTF8String(data, 0, data.length);
}
}
gen.writeEndObject();
}
}

View File

@ -31,9 +31,14 @@ import java.util.Set;
public final class JsonFormat implements EventFormat {
public static final String CONTENT_TYPE = "application/cloudevents+json";
public static final ObjectMapper MAPPER = new ObjectMapper();
static {
// Add CloudEvent serializer/deserializer
MAPPER.registerModule(getCloudEventJacksonModule());
// add ZonedDateTime ser/de
final SimpleModule module = new SimpleModule("Custom ZonedDateTime");
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer());
@ -41,8 +46,8 @@ public final class JsonFormat implements EventFormat {
MAPPER.registerModule(module);
}
private boolean forceDataBase64Serialization = false;
private boolean forceStringSerialization = false;
private static boolean forceDataBase64Serialization = false;
private static boolean forceStringSerialization = false;
@Override
public byte[] serialize(CloudEvent event) throws EventSerializationException {
@ -64,16 +69,34 @@ public final class JsonFormat implements EventFormat {
@Override
public Set<String> supportedContentTypes() {
return Collections.singleton("application/cloudevents+json");
return Collections.singleton(CONTENT_TYPE);
}
public JsonFormat forceDataBase64Serialization(boolean forceBase64Serialization) {
this.forceDataBase64Serialization = forceBase64Serialization;
return this;
public static SimpleModule getCloudEventJacksonModule() {
final SimpleModule ceModule = new SimpleModule("CloudEvent");
ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer());
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer());
return ceModule;
}
public JsonFormat forceDataStringSerialization(boolean forceStringSerialization) {
this.forceStringSerialization = forceStringSerialization;
return this;
public static void forceDataBase64Serialization(boolean forceBase64Serialization) {
JsonFormat.forceDataBase64Serialization = forceBase64Serialization;
}
public static void forceDataStringSerialization(boolean forceStringSerialization) {
JsonFormat.forceStringSerialization = forceStringSerialization;
}
protected static boolean isJsonContentType(String contentType) {
// If content type, spec states that we should assume is json
return contentType == null || contentType.startsWith("application/json") || contentType.startsWith("text/json");
}
protected static boolean shouldSerializeBase64(String contentType) {
if (isJsonContentType(contentType)) {
return JsonFormat.forceDataBase64Serialization;
} else {
return !JsonFormat.forceStringSerialization;
}
}
}

View File

@ -123,7 +123,7 @@ class JsonFormatTest {
}
private EventFormat getFormat() {
return EventFormatProvider.getInstance().resolveFormat("application/cloudevents+json");
return EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
}
}