Skip to content

Commit

Permalink
Add deserialize options for jackson CloudEventDeserializer
Browse files Browse the repository at this point in the history
Signed-off-by: mhyeon-lee <[email protected]>
  • Loading branch information
mhyeon-lee committed Dec 8, 2021
1 parent 20f367d commit 2c71c8a
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,19 +38,35 @@
* Jackson {@link com.fasterxml.jackson.databind.JsonDeserializer} for {@link CloudEvent}
*/
class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
private final boolean forceExtensionNameLowerCaseDeserialization;
private final boolean forceIgnoreInvalidExtensionNameDeserialization;

protected CloudEventDeserializer() {
protected CloudEventDeserializer(
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
) {
super(CloudEvent.class);
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
}

private static class JsonMessage implements CloudEventReader {

private final JsonParser p;
private final ObjectNode node;

public JsonMessage(JsonParser p, ObjectNode node) {
private final boolean forceExtensionNameLowerCaseDeserialization;
private final boolean forceIgnoreInvalidExtensionNameDeserialization;

public JsonMessage(
JsonParser p,
ObjectNode node,
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
) {
this.p = p;
this.node = node;
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
}

@Override
Expand Down Expand Up @@ -126,10 +142,12 @@ public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> w

// Now let's process the extensions
node.fields().forEachRemaining(entry -> {
String extensionName = entry.getKey().toLowerCase();
String extensionName = entry.getKey();
if (this.forceExtensionNameLowerCaseDeserialization) {
extensionName = extensionName.toLowerCase();
}

// ignore not valid extension name
if (!this.isValidExtensionName(extensionName)) {
if (this.shouldSkipExtensionName(extensionName)) {
return;
}

Expand Down Expand Up @@ -199,6 +217,11 @@ private void assertNodeType(JsonNode node, JsonNodeType type, String attributeNa
}
}

// ignore not valid extension name
private boolean shouldSkipExtensionName(String extensionName) {
return this.forceIgnoreInvalidExtensionNameDeserialization && !this.isValidExtensionName(extensionName);
}

/**
* Validates the extension name as defined in CloudEvents spec.
*
Expand Down Expand Up @@ -228,7 +251,8 @@ public CloudEvent deserialize(JsonParser p, DeserializationContext ctxt) throws
ObjectNode node = ctxt.readValue(p, ObjectNode.class);

try {
return new JsonMessage(p, node).read(CloudEventBuilder::fromSpecVersion);
return new JsonMessage(p, node, this.forceExtensionNameLowerCaseDeserialization, this.forceIgnoreInvalidExtensionNameDeserialization)
.read(CloudEventBuilder::fromSpecVersion);
} catch (RuntimeException e) {
// Yeah this is bad but it's needed to support checked exceptions...
if (e.getCause() instanceof IOException) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
* using Jackson. This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #CONTENT_TYPE}.
* <p>
* If you want to use the {@link CloudEvent} serializers/deserializers directly in your mapper, you can use {@link #getCloudEventJacksonModule()} or
* {@link #getCloudEventJacksonModule(boolean, boolean)} to get a {@link SimpleModule} to register in your {@link ObjectMapper} instance.
* {@link #getCloudEventJacksonModule(boolean, boolean, boolean, boolean)} to get a {@link SimpleModule} to register in your {@link ObjectMapper} instance.
*/
public final class JsonFormat implements EventFormat {

Expand All @@ -47,41 +47,95 @@ public final class JsonFormat implements EventFormat {
private final ObjectMapper mapper;
private final boolean forceDataBase64Serialization;
private final boolean forceStringSerialization;
private final boolean forceExtensionNameLowerCaseDeserialization;
private final boolean forceIgnoreInvalidExtensionNameDeserialization;

/**
* Create a new instance of this class customizing the serialization configuration.
*
* @param forceDataBase64Serialization force json base64 encoding for data
* @param forceStringSerialization force string serialization for non json data field
* @param forceExtensionNameLowerCaseDeserialization force extension name deserialization for lower case
* @param forceIgnoreInvalidExtensionNameDeserialization force extension name deserialization for ignoring invalid name
* @see #withForceJsonDataToBase64()
* @see #withForceNonJsonDataToString()
* @see #withForceExtensionNameLowerCaseDeserialization()
* @see #withForceIgnoreInvalidExtensionNameDeserialization()
*/
public JsonFormat(boolean forceDataBase64Serialization, boolean forceStringSerialization) {
public JsonFormat(
boolean forceDataBase64Serialization,
boolean forceStringSerialization,
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
) {
this.mapper = new ObjectMapper();
this.mapper.registerModule(getCloudEventJacksonModule(forceDataBase64Serialization, forceStringSerialization));
this.mapper.registerModule(
getCloudEventJacksonModule(
forceDataBase64Serialization,
forceStringSerialization,
forceExtensionNameLowerCaseDeserialization,
forceIgnoreInvalidExtensionNameDeserialization
)
);
this.forceDataBase64Serialization = forceDataBase64Serialization;
this.forceStringSerialization = forceStringSerialization;
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
}

/**
* Create a new instance of this class with default serialization configuration
*/
public JsonFormat() {
this(false, false);
this(false, false, false, false);
}

/**
* @return a copy of this JsonFormat that serialize events with json data with Base64 encoding
*/
public JsonFormat withForceJsonDataToBase64() {
return new JsonFormat(true, this.forceStringSerialization);
return new JsonFormat(
true,
this.forceStringSerialization,
this.forceExtensionNameLowerCaseDeserialization,
this.forceIgnoreInvalidExtensionNameDeserialization
);
}

/**
* @return a copy of this JsonFormat that serialize events with non-json data as string
*/
public JsonFormat withForceNonJsonDataToString() {
return new JsonFormat(this.forceDataBase64Serialization, true);
return new JsonFormat(
this.forceDataBase64Serialization,
true,
this.forceExtensionNameLowerCaseDeserialization,
this.forceIgnoreInvalidExtensionNameDeserialization
);
}

/**
* @return a copy of this JsonFormat that deserialize events with converting extension name lower case.
*/
public JsonFormat withForceExtensionNameLowerCaseDeserialization() {
return new JsonFormat(
this.forceDataBase64Serialization,
this.forceStringSerialization,
true,
this.forceIgnoreInvalidExtensionNameDeserialization
);
}

/**
* @return a copy of this JsonFormat that deserialize events with ignoring invalid extension name
*/
public JsonFormat withForceIgnoreInvalidExtensionNameDeserialization() {
return new JsonFormat(
this.forceDataBase64Serialization,
this.forceStringSerialization,
this.forceExtensionNameLowerCaseDeserialization,
true
);
}

@Override
Expand Down Expand Up @@ -126,20 +180,30 @@ public String serializedContentType() {
* @return a {@link SimpleModule} with {@link CloudEvent} serializer/deserializer configured using default values.
*/
public static SimpleModule getCloudEventJacksonModule() {
return getCloudEventJacksonModule(false, false);
return getCloudEventJacksonModule(false, false, false, false);
}

/**
* @param forceDataBase64Serialization force json base64 encoding for data
* @param forceStringSerialization force string serialization for non json data field
* @param forceExtensionNameLowerCaseDeserialization force extension name deserialization for lower case
* @param forceIgnoreInvalidExtensionNameDeserialization force extension name deserialization for ignoring invalid name
* @return a JacksonModule with CloudEvent serializer/deserializer customizing the data serialization.
* @see #withForceJsonDataToBase64()
* @see #withForceNonJsonDataToString()
* @see #withForceExtensionNameLowerCaseDeserialization()
* @see #withForceIgnoreInvalidExtensionNameDeserialization()
*/
public static SimpleModule getCloudEventJacksonModule(boolean forceDataBase64Serialization, boolean forceStringSerialization) {
public static SimpleModule getCloudEventJacksonModule(
boolean forceDataBase64Serialization,
boolean forceStringSerialization,
boolean forceExtensionNameLowerCaseDeserialization,
boolean forceIgnoreInvalidExtensionNameDeserialization
) {
final SimpleModule ceModule = new SimpleModule("CloudEvent");
ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer(forceDataBase64Serialization, forceStringSerialization));
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer());
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer(
forceExtensionNameLowerCaseDeserialization, forceIgnoreInvalidExtensionNameDeserialization));
return ceModule;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,12 @@
import io.cloudevents.core.format.EventDeserializationException;
import io.cloudevents.core.provider.EventFormatProvider;
import io.cloudevents.rw.CloudEventRWException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.IOException;
import java.math.BigInteger;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
Expand Down Expand Up @@ -90,6 +88,22 @@ void deserialize(String inputFile, CloudEvent output) {
.isEqualTo(output);
}

@ParameterizedTest
@MethodSource("deserializeTestArgumentsUpperCaseExtensionName")
void deserializeWithUpperCaseExtensionName(String inputFile, CloudEvent output) {
CloudEvent deserialized = getFormat().withForceExtensionNameLowerCaseDeserialization().deserialize(loadFile(inputFile));
assertThat(deserialized)
.isEqualTo(output);
}

@ParameterizedTest
@MethodSource("deserializeTestArgumentsInvalidExtensionName")
void deserializeWithInvalidExtensionName(String inputFile, CloudEvent output) {
CloudEvent deserialized = getFormat().withForceIgnoreInvalidExtensionNameDeserialization().deserialize(loadFile(inputFile));
assertThat(deserialized)
.isEqualTo(output);
}

@ParameterizedTest
@MethodSource("roundTripTestArguments")
void jsonRoundTrip(String inputFile) throws IOException {
Expand Down Expand Up @@ -204,6 +218,20 @@ public static Stream<Arguments> deserializeTestArguments() {
);
}

public static Stream<Arguments> deserializeTestArgumentsUpperCaseExtensionName() {
return Stream.of(
Arguments.of("v03/json_data_with_ext_upper_case.json", normalizeToJsonValueIfNeeded(V03_WITH_JSON_DATA_WITH_EXT)),
Arguments.of("v1/json_data_with_ext_upper_case.json", normalizeToJsonValueIfNeeded(V1_WITH_JSON_DATA_WITH_EXT))
);
}

public static Stream<Arguments> deserializeTestArgumentsInvalidExtensionName() {
return Stream.of(
Arguments.of("v03/json_data_with_ext_invalid.json", normalizeToJsonValueIfNeeded(V03_WITH_JSON_DATA_WITH_EXT)),
Arguments.of("v1/json_data_with_ext_invalid.json", normalizeToJsonValueIfNeeded(V1_WITH_JSON_DATA_WITH_EXT))
);
}

public static Stream<String> roundTripTestArguments() {
return Stream.of(
"v03/min.json",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"specversion": "0.3",
"id": "1",
"type": "mock.test",
"source": "http://localhost/source",
"schemaurl": "http://localhost/schema",
"datacontenttype": "application/json",
"data": {},
"subject": "sub",
"time": "2018-04-26T14:48:09+02:00",
"astring": "aaa",
"aboolean": true,
"anumber": 10,
"a_invalid_name": "invalidName"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"specversion": "0.3",
"id": "1",
"type": "mock.test",
"source": "http://localhost/source",
"schemaurl": "http://localhost/schema",
"datacontenttype": "application/json",
"data": {},
"subject": "sub",
"time": "2018-04-26T14:48:09+02:00",
"aString": "aaa",
"aBoolean": true,
"aNumber": 10
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"specversion": "1.0",
"id": "1",
"type": "mock.test",
"source": "http://localhost/source",
"dataschema": "http://localhost/schema",
"datacontenttype": "application/json",
"data": {},
"subject": "sub",
"time": "2018-04-26T14:48:09+02:00",
"astring": "aaa",
"aboolean": true,
"anumber": 10,
"a_invalid_name": "invalidName"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"specversion": "1.0",
"id": "1",
"type": "mock.test",
"source": "http://localhost/source",
"dataschema": "http://localhost/schema",
"datacontenttype": "application/json",
"data": {},
"subject": "sub",
"time": "2018-04-26T14:48:09+02:00",
"aString": "aaa",
"aBoolean": true,
"aNumber": 10
}

0 comments on commit 2c71c8a

Please sign in to comment.