From 20f367da2457b047c6a579d90d7dc7384a78ddd6 Mon Sep 17 00:00:00 2001 From: mhyeon-lee Date: Wed, 8 Dec 2021 00:15:43 +0900 Subject: [PATCH 1/3] Ignore not valid extension name in jackson CloudEventDeserializer Signed-off-by: mhyeon-lee --- .../jackson/CloudEventDeserializer.java | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java index 87d9a9196..b5ad5a644 100644 --- a/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java @@ -126,7 +126,13 @@ public , V> V read(CloudEventWriterFactory w // Now let's process the extensions node.fields().forEachRemaining(entry -> { - String extensionName = entry.getKey(); + String extensionName = entry.getKey().toLowerCase(); + + // ignore not valid extension name + if (!this.isValidExtensionName(extensionName)) { + return; + } + JsonNode extensionValue = entry.getValue(); switch (extensionValue.getNodeType()) { @@ -192,6 +198,27 @@ private void assertNodeType(JsonNode node, JsonNodeType type, String attributeNa ); } } + + /** + * Validates the extension name as defined in CloudEvents spec. + * + * @param name the extension name + * @return true if extension name is valid, false otherwise + * @see attribute-naming-convention + */ + private boolean isValidExtensionName(String name) { + for (int i = 0; i < name.length(); i++) { + if (!isValidChar(name.charAt(i))) { + return false; + } + } + return true; + } + + private boolean isValidChar(char c) { + return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9'); + } + } @Override From 2c71c8af092cc6b831532510e3576e7385fc8b40 Mon Sep 17 00:00:00 2001 From: mhyeon-lee Date: Wed, 8 Dec 2021 15:22:11 +0900 Subject: [PATCH 2/3] Add deserialize options for jackson CloudEventDeserializer Signed-off-by: mhyeon-lee --- .../jackson/CloudEventDeserializer.java | 38 +++++++-- .../io/cloudevents/jackson/JsonFormat.java | 82 +++++++++++++++++-- .../cloudevents/jackson/JsonFormatTest.java | 32 +++++++- .../v03/json_data_with_ext_invalid.json | 15 ++++ .../v03/json_data_with_ext_upper_case.json | 14 ++++ .../v1/json_data_with_ext_invalid.json | 15 ++++ .../v1/json_data_with_ext_upper_case.json | 14 ++++ 7 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 formats/json-jackson/src/test/resources/v03/json_data_with_ext_invalid.json create mode 100644 formats/json-jackson/src/test/resources/v03/json_data_with_ext_upper_case.json create mode 100644 formats/json-jackson/src/test/resources/v1/json_data_with_ext_invalid.json create mode 100644 formats/json-jackson/src/test/resources/v1/json_data_with_ext_upper_case.json diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java index b5ad5a644..020cd1c90 100644 --- a/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java @@ -38,19 +38,35 @@ * Jackson {@link com.fasterxml.jackson.databind.JsonDeserializer} for {@link CloudEvent} */ class CloudEventDeserializer extends StdDeserializer { + 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 @@ -126,10 +142,12 @@ public , V> V read(CloudEventWriterFactory 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; } @@ -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. * @@ -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) { diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java index a374b6c1e..220b94123 100644 --- a/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java @@ -35,7 +35,7 @@ * using Jackson. This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #CONTENT_TYPE}. *

* 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 { @@ -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 @@ -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; } diff --git a/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java b/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java index de99403de..318cffcc5 100644 --- a/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java +++ b/formats/json-jackson/src/test/java/io/cloudevents/jackson/JsonFormatTest.java @@ -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; @@ -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 { @@ -204,6 +218,20 @@ public static Stream deserializeTestArguments() { ); } + public static Stream 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 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 roundTripTestArguments() { return Stream.of( "v03/min.json", diff --git a/formats/json-jackson/src/test/resources/v03/json_data_with_ext_invalid.json b/formats/json-jackson/src/test/resources/v03/json_data_with_ext_invalid.json new file mode 100644 index 000000000..a55bfae32 --- /dev/null +++ b/formats/json-jackson/src/test/resources/v03/json_data_with_ext_invalid.json @@ -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" +} diff --git a/formats/json-jackson/src/test/resources/v03/json_data_with_ext_upper_case.json b/formats/json-jackson/src/test/resources/v03/json_data_with_ext_upper_case.json new file mode 100644 index 000000000..1cf194184 --- /dev/null +++ b/formats/json-jackson/src/test/resources/v03/json_data_with_ext_upper_case.json @@ -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 +} diff --git a/formats/json-jackson/src/test/resources/v1/json_data_with_ext_invalid.json b/formats/json-jackson/src/test/resources/v1/json_data_with_ext_invalid.json new file mode 100644 index 000000000..4eda33f05 --- /dev/null +++ b/formats/json-jackson/src/test/resources/v1/json_data_with_ext_invalid.json @@ -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" +} diff --git a/formats/json-jackson/src/test/resources/v1/json_data_with_ext_upper_case.json b/formats/json-jackson/src/test/resources/v1/json_data_with_ext_upper_case.json new file mode 100644 index 000000000..3bcc44817 --- /dev/null +++ b/formats/json-jackson/src/test/resources/v1/json_data_with_ext_upper_case.json @@ -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 +} From f06343a52a583e5e7b75b3ab33a3bf9bc7e5392a Mon Sep 17 00:00:00 2001 From: mhyeon-lee Date: Fri, 10 Dec 2021 09:35:31 +0900 Subject: [PATCH 3/3] Introduce JsonFormatOptions with builder Signed-off-by: mhyeon-lee --- .../jackson/CloudEventDeserializer.java | 4 + .../io/cloudevents/jackson/JsonFormat.java | 119 +++++++++--------- .../jackson/JsonFormatOptions.java | 99 +++++++++++++++ 3 files changed, 165 insertions(+), 57 deletions(-) create mode 100644 formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormatOptions.java diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java index 020cd1c90..1383d7cb1 100644 --- a/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/CloudEventDeserializer.java @@ -41,6 +41,10 @@ class CloudEventDeserializer extends StdDeserializer { private final boolean forceExtensionNameLowerCaseDeserialization; private final boolean forceIgnoreInvalidExtensionNameDeserialization; + protected CloudEventDeserializer() { + this(false, false); + } + protected CloudEventDeserializer( boolean forceExtensionNameLowerCaseDeserialization, boolean forceIgnoreInvalidExtensionNameDeserialization diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java index 220b94123..49467980b 100644 --- a/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormat.java @@ -35,7 +35,7 @@ * using Jackson. This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #CONTENT_TYPE}. *

* If you want to use the {@link CloudEvent} serializers/deserializers directly in your mapper, you can use {@link #getCloudEventJacksonModule()} or - * {@link #getCloudEventJacksonModule(boolean, boolean, boolean, boolean)} to get a {@link SimpleModule} to register in your {@link ObjectMapper} instance. + * {@link #getCloudEventJacksonModule(boolean, boolean)} to get a {@link SimpleModule} to register in your {@link ObjectMapper} instance. */ public final class JsonFormat implements EventFormat { @@ -45,49 +45,41 @@ public final class JsonFormat implements EventFormat { public static final String CONTENT_TYPE = "application/cloudevents+json"; private final ObjectMapper mapper; - private final boolean forceDataBase64Serialization; - private final boolean forceStringSerialization; - private final boolean forceExtensionNameLowerCaseDeserialization; - private final boolean forceIgnoreInvalidExtensionNameDeserialization; + private final JsonFormatOptions options; /** * 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, - boolean forceExtensionNameLowerCaseDeserialization, - boolean forceIgnoreInvalidExtensionNameDeserialization - ) { - this.mapper = new ObjectMapper(); - this.mapper.registerModule( - getCloudEventJacksonModule( - forceDataBase64Serialization, - forceStringSerialization, - forceExtensionNameLowerCaseDeserialization, - forceIgnoreInvalidExtensionNameDeserialization - ) + public JsonFormat(boolean forceDataBase64Serialization, boolean forceStringSerialization) { + this( + JsonFormatOptions.builder() + .forceDataBase64Serialization(forceDataBase64Serialization) + .forceStringSerialization(forceStringSerialization) + .build() ); - this.forceDataBase64Serialization = forceDataBase64Serialization; - this.forceStringSerialization = forceStringSerialization; - this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization; - this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization; + } + + /** + * Create a new instance of this class customizing the serialization configuration. + * + * @param options json serialization / deserialization options + */ + public JsonFormat(JsonFormatOptions options) { + this.mapper = new ObjectMapper(); + this.mapper.registerModule(getCloudEventJacksonModule(options)); + this.options = options; } /** * Create a new instance of this class with default serialization configuration */ public JsonFormat() { - this(false, false, false, false); + this(new JsonFormatOptions()); } /** @@ -95,10 +87,12 @@ public JsonFormat() { */ public JsonFormat withForceJsonDataToBase64() { return new JsonFormat( - true, - this.forceStringSerialization, - this.forceExtensionNameLowerCaseDeserialization, - this.forceIgnoreInvalidExtensionNameDeserialization + JsonFormatOptions.builder() + .forceDataBase64Serialization(true) + .forceStringSerialization(this.options.isForceStringSerialization()) + .forceExtensionNameLowerCaseDeserialization(this.options.isForceExtensionNameLowerCaseDeserialization()) + .forceIgnoreInvalidExtensionNameDeserialization(this.options.isForceIgnoreInvalidExtensionNameDeserialization()) + .build() ); } @@ -107,10 +101,12 @@ public JsonFormat withForceJsonDataToBase64() { */ public JsonFormat withForceNonJsonDataToString() { return new JsonFormat( - this.forceDataBase64Serialization, - true, - this.forceExtensionNameLowerCaseDeserialization, - this.forceIgnoreInvalidExtensionNameDeserialization + JsonFormatOptions.builder() + .forceDataBase64Serialization(this.options.isForceDataBase64Serialization()) + .forceStringSerialization(true) + .forceExtensionNameLowerCaseDeserialization(this.options.isForceExtensionNameLowerCaseDeserialization()) + .forceIgnoreInvalidExtensionNameDeserialization(this.options.isForceIgnoreInvalidExtensionNameDeserialization()) + .build() ); } @@ -119,10 +115,12 @@ public JsonFormat withForceNonJsonDataToString() { */ public JsonFormat withForceExtensionNameLowerCaseDeserialization() { return new JsonFormat( - this.forceDataBase64Serialization, - this.forceStringSerialization, - true, - this.forceIgnoreInvalidExtensionNameDeserialization + JsonFormatOptions.builder() + .forceDataBase64Serialization(this.options.isForceDataBase64Serialization()) + .forceStringSerialization(this.options.isForceStringSerialization()) + .forceExtensionNameLowerCaseDeserialization(true) + .forceIgnoreInvalidExtensionNameDeserialization(this.options.isForceIgnoreInvalidExtensionNameDeserialization()) + .build() ); } @@ -131,10 +129,12 @@ public JsonFormat withForceExtensionNameLowerCaseDeserialization() { */ public JsonFormat withForceIgnoreInvalidExtensionNameDeserialization() { return new JsonFormat( - this.forceDataBase64Serialization, - this.forceStringSerialization, - this.forceExtensionNameLowerCaseDeserialization, - true + JsonFormatOptions.builder() + .forceDataBase64Serialization(this.options.isForceDataBase64Serialization()) + .forceStringSerialization(this.options.isForceStringSerialization()) + .forceExtensionNameLowerCaseDeserialization(this.options.isForceExtensionNameLowerCaseDeserialization()) + .forceIgnoreInvalidExtensionNameDeserialization(true) + .build() ); } @@ -180,30 +180,35 @@ public String serializedContentType() { * @return a {@link SimpleModule} with {@link CloudEvent} serializer/deserializer configured using default values. */ public static SimpleModule getCloudEventJacksonModule() { - return getCloudEventJacksonModule(false, false, false, false); + return getCloudEventJacksonModule(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, - boolean forceExtensionNameLowerCaseDeserialization, - boolean forceIgnoreInvalidExtensionNameDeserialization - ) { + public static SimpleModule getCloudEventJacksonModule(boolean forceDataBase64Serialization, boolean forceStringSerialization) { + return getCloudEventJacksonModule( + JsonFormatOptions.builder() + .forceDataBase64Serialization(forceDataBase64Serialization) + .forceStringSerialization(forceStringSerialization) + .build() + ); + } + + /** + * @param options json serialization / deserialization options + * @return a JacksonModule with CloudEvent serializer/deserializer customizing the data serialization. + */ + public static SimpleModule getCloudEventJacksonModule(JsonFormatOptions options) { final SimpleModule ceModule = new SimpleModule("CloudEvent"); - ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer(forceDataBase64Serialization, forceStringSerialization)); + ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer( + options.isForceDataBase64Serialization(), options.isForceStringSerialization())); ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer( - forceExtensionNameLowerCaseDeserialization, forceIgnoreInvalidExtensionNameDeserialization)); + options.isForceExtensionNameLowerCaseDeserialization(), options.isForceIgnoreInvalidExtensionNameDeserialization())); return ceModule; } diff --git a/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormatOptions.java b/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormatOptions.java new file mode 100644 index 000000000..bf18ebf31 --- /dev/null +++ b/formats/json-jackson/src/main/java/io/cloudevents/jackson/JsonFormatOptions.java @@ -0,0 +1,99 @@ +/* + * Copyright 2018-Present The CloudEvents Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package io.cloudevents.jackson; + +public final class JsonFormatOptions { + private final boolean forceDataBase64Serialization; + private final boolean forceStringSerialization; + private final boolean forceExtensionNameLowerCaseDeserialization; + private final boolean forceIgnoreInvalidExtensionNameDeserialization; + + /** + * Create a new instance of this class options the serialization / deserialization. + */ + public JsonFormatOptions() { + this(false, false, false, false); + } + + JsonFormatOptions( + boolean forceDataBase64Serialization, + boolean forceStringSerialization, + boolean forceExtensionNameLowerCaseDeserialization, + boolean forceIgnoreInvalidExtensionNameDeserialization + ) { + this.forceDataBase64Serialization = forceDataBase64Serialization; + this.forceStringSerialization = forceStringSerialization; + this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization; + this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization; + } + + public static JsonFormatOptionsBuilder builder() { + return new JsonFormatOptionsBuilder(); + } + + public boolean isForceDataBase64Serialization() { + return this.forceDataBase64Serialization; + } + + public boolean isForceStringSerialization() { + return this.forceStringSerialization; + } + + public boolean isForceExtensionNameLowerCaseDeserialization() { + return this.forceExtensionNameLowerCaseDeserialization; + } + + public boolean isForceIgnoreInvalidExtensionNameDeserialization() { + return this.forceIgnoreInvalidExtensionNameDeserialization; + } + + public static class JsonFormatOptionsBuilder { + private boolean forceDataBase64Serialization = false; + private boolean forceStringSerialization = false; + private boolean forceExtensionNameLowerCaseDeserialization = false; + private boolean forceIgnoreInvalidExtensionNameDeserialization = false; + + public JsonFormatOptionsBuilder forceDataBase64Serialization(boolean forceDataBase64Serialization) { + this.forceDataBase64Serialization = forceDataBase64Serialization; + return this; + } + + public JsonFormatOptionsBuilder forceStringSerialization(boolean forceStringSerialization) { + this.forceStringSerialization = forceStringSerialization; + return this; + } + + public JsonFormatOptionsBuilder forceExtensionNameLowerCaseDeserialization(boolean forceExtensionNameLowerCaseDeserialization) { + this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization; + return this; + } + + public JsonFormatOptionsBuilder forceIgnoreInvalidExtensionNameDeserialization(boolean forceIgnoreInvalidExtensionNameDeserialization) { + this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization; + return this; + } + + public JsonFormatOptions build() { + return new JsonFormatOptions( + this.forceDataBase64Serialization, + this.forceStringSerialization, + this.forceExtensionNameLowerCaseDeserialization, + this.forceIgnoreInvalidExtensionNameDeserialization + ); + } + } +}