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; private HashMap<String, EventFormat> formats;
//TODO register stuff with SPI
private EventFormatProvider() { private EventFormatProvider() {
this.formats = new HashMap<>(); this.formats = new HashMap<>();

View File

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

View File

@ -3,10 +3,9 @@ package io.cloudevents.message;
public interface BinaryMessageAttributes { public interface BinaryMessageAttributes {
/** /**
*
* @param visitor * @param visitor
* @throws MessageVisitException * @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.net.URI;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
@FunctionalInterface
public interface BinaryMessageAttributesVisitor { public interface BinaryMessageAttributesVisitor {
void setAttribute(String name, String value) throws MessageVisitException; 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; package io.cloudevents.message;
@FunctionalInterface
public interface BinaryMessageExtensionsVisitor { public interface BinaryMessageExtensionsVisitor {
void setExtension(String name, String value) throws MessageVisitException; void setExtension(String name, String value) throws MessageVisitException;

View File

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

View File

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

View File

@ -1,23 +1,128 @@
package io.cloudevents.format.json; package io.cloudevents.format.json;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer; import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import io.cloudevents.CloudEvent; 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.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
public class CloudEventSerializer extends StdSerializer<CloudEvent> { public class CloudEventSerializer extends StdSerializer<CloudEvent> {
protected CloudEventSerializer() { protected CloudEventSerializer() {
super(CloudEvent.class); 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 @Override
public void serialize(CloudEvent value, JsonGenerator gen, SerializerProvider provider) throws IOException { public void serialize(CloudEvent value, JsonGenerator gen, SerializerProvider provider) throws IOException {
JsonSerializer<Object> attributesSerializer = provider.findValueSerializer(value.getAttributes().getClass()); gen.writeStartObject();
// Serialize attributes gen.writeStringField("specversion", value.getAttributes().getSpecVersion().toString());
attributesSerializer.serialize(value.getAttributes(), gen, provider);
// 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 final class JsonFormat implements EventFormat {
public static final String CONTENT_TYPE = "application/cloudevents+json";
public static final ObjectMapper MAPPER = new ObjectMapper(); public static final ObjectMapper MAPPER = new ObjectMapper();
static { static {
// Add CloudEvent serializer/deserializer
MAPPER.registerModule(getCloudEventJacksonModule());
// add ZonedDateTime ser/de // add ZonedDateTime ser/de
final SimpleModule module = new SimpleModule("Custom ZonedDateTime"); final SimpleModule module = new SimpleModule("Custom ZonedDateTime");
module.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer()); module.addSerializer(ZonedDateTime.class, new ZonedDateTimeSerializer());
@ -41,8 +46,8 @@ public final class JsonFormat implements EventFormat {
MAPPER.registerModule(module); MAPPER.registerModule(module);
} }
private boolean forceDataBase64Serialization = false; private static boolean forceDataBase64Serialization = false;
private boolean forceStringSerialization = false; private static boolean forceStringSerialization = false;
@Override @Override
public byte[] serialize(CloudEvent event) throws EventSerializationException { public byte[] serialize(CloudEvent event) throws EventSerializationException {
@ -64,16 +69,34 @@ public final class JsonFormat implements EventFormat {
@Override @Override
public Set<String> supportedContentTypes() { public Set<String> supportedContentTypes() {
return Collections.singleton("application/cloudevents+json"); return Collections.singleton(CONTENT_TYPE);
} }
public JsonFormat forceDataBase64Serialization(boolean forceBase64Serialization) { public static SimpleModule getCloudEventJacksonModule() {
this.forceDataBase64Serialization = forceBase64Serialization; final SimpleModule ceModule = new SimpleModule("CloudEvent");
return this; ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer());
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer());
return ceModule;
} }
public JsonFormat forceDataStringSerialization(boolean forceStringSerialization) { public static void forceDataBase64Serialization(boolean forceBase64Serialization) {
this.forceStringSerialization = forceStringSerialization; JsonFormat.forceDataBase64Serialization = forceBase64Serialization;
return this; }
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() { private EventFormat getFormat() {
return EventFormatProvider.getInstance().resolveFormat("application/cloudevents+json"); return EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
} }
} }