From 989c0fa519e52dfbf39e15724034c1a32ef6355b Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 7 Apr 2023 10:24:24 -0700 Subject: [PATCH 01/28] feat: Add Avro EvenFormat Signed-off-by: Alex Collins --- docs/avro.md | 96 ++++++++++ formats/avro/README.md | 9 + formats/avro/pom.xml | 97 ++++++++++ formats/avro/src/main/avro/cloudevents.avsc | 73 ++++++++ .../java/io/cloudevents/avro/AvroFormat.java | 168 ++++++++++++++++++ .../io.cloudevents.core.format.EventFormat | 1 + .../io/cloudevents/avro/AvroFormatTest.java | 76 ++++++++ pom.xml | 1 + 8 files changed, 521 insertions(+) create mode 100644 docs/avro.md create mode 100644 formats/avro/README.md create mode 100644 formats/avro/pom.xml create mode 100644 formats/avro/src/main/avro/cloudevents.avsc create mode 100644 formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java create mode 100644 formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat create mode 100644 formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java diff --git a/docs/avro.md b/docs/avro.md new file mode 100644 index 000000000..203cab0ca --- /dev/null +++ b/docs/avro.md @@ -0,0 +1,96 @@ +--- +title: CloudEvents Avro +nav_order: 4 +--- + +# CloudEvents Avro + +[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-avro.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro) + +This module provides the Avro Buffer (avro) `EventFormat` implementation using the Java +avro runtime and classes generated from the CloudEvents +[avro spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.avro). + +# Setup +For Maven based projects, use the following dependency: + +```xml + + io.cloudevents + cloudevents-avro + x.y.z + +``` + +No further configuration is required is use the module. + +## Using the avro Event Format + +### Event serialization + +```java +import io.cloudevents.CloudEvent; +import io.cloudevents.core.format.EventFormatProvider; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.avro.avroFormat; + +CloudEvent event = CloudEventBuilder.v1() + .withId("hello") + .withType("example.vertx") + .withSource(URI.create("http://localhost")) + .build(); + +byte[]serialized = EventFormatProvider + .getInstance() + .resolveFormat(avroFormat.CONTENT_TYPE) + .serialize(event); +``` + +The `EventFormatProvider` will automatically resolve the `avroFormat` using the +`ServiceLoader` APIs. + +## Passing avro messages as CloudEvent data. + +The `AvroCloudEventData` capability provides a convenience mechanism to handle avro message object data. + +### Building + +```java +// Build my business event message. +com.google.avro.Message myMessage = ..... ; + +// Wrap the avro message as CloudEventData. +CloudEventData ceData = AvroCloudEventData.wrap(myMessage); + +// Build the CloudEvent +CloudEvent event = CloudEventBuilder.v1() + .withId("hello") + .withType("example.avrodata") + .withSource(URI.create("http://localhost")) + .withData(ceData) + .build(); +``` + +### Reading + +If the `AvroFormat` is used to deserialize a CloudEvent that contains a avro message object as data you can use +the `AvroCloudEventData` to access it as an 'Any' directly. + +```java + +// Deserialize the event. +CloudEvent myEvent = eventFormat.deserialize(raw); + +// Get the Data +CloudEventData eventData = myEvent.getData(); + +if (ceData instanceOf AvroCloudEventData) { + + // Obtain the avro 'any' + Any anAny = ((AvroCloudEventData) eventData).getAny(); + + ... +} + +``` + diff --git a/formats/avro/README.md b/formats/avro/README.md new file mode 100644 index 000000000..81ad02619 --- /dev/null +++ b/formats/avro/README.md @@ -0,0 +1,9 @@ +# CloudEvents Avro Format + +This project provides functionality for the Java SDK to handle the +[avro format](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/avro-format.md). + +The Avro definition file is located in src/main/avro/spec.proto. The file was directly +copied from [https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/cloudevents.avsc](). + +The namespace has been changed so it does not clash with the core namespace. diff --git a/formats/avro/pom.xml b/formats/avro/pom.xml new file mode 100644 index 000000000..7db6ef1df --- /dev/null +++ b/formats/avro/pom.xml @@ -0,0 +1,97 @@ + + + + 4.0.0 + + + io.cloudevents + cloudevents-parent + 2.5.0-SNAPSHOT + ../../pom.xml + + + cloudevents-avro + CloudEvents - Avro + + + + + org.apache.avro + avro-maven-plugin + 1.11.1 + + String + + + + generate-sources + + schema + + + + + + + + + + io.cloudevents + cloudevents-core + ${project.version} + + + org.apache.avro + avro + 1.11.1 + + + + + org.slf4j + slf4j-simple + 1.7.36 + test + + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + io.cloudevents + cloudevents-core + tests + test-jar + ${project.version} + test + + + + + diff --git a/formats/avro/src/main/avro/cloudevents.avsc b/formats/avro/src/main/avro/cloudevents.avsc new file mode 100644 index 000000000..4eb2d705c --- /dev/null +++ b/formats/avro/src/main/avro/cloudevents.avsc @@ -0,0 +1,73 @@ +{ + "namespace": "io.cloudevents.v1.avro", + "type": "record", + "name": "CloudEvent", + "version": "1.0", + "doc": "Avro Event Format for CloudEvents", + "fields": [ + { + "name": "attribute", + "type": { + "type": "map", + "values": [ + "null", + "boolean", + "int", + "string", + "bytes" + ] + } + }, + { + "name": "data", + "type": [ + "bytes", + "null", + "boolean", + { + "type": "map", + "values": [ + "null", + "boolean", + { + "type": "record", + "name": "CloudEventData", + "doc": "Representation of a JSON Value", + "fields": [ + { + "name": "value", + "type": { + "type": "map", + "values": [ + "null", + "boolean", + { + "type": "map", + "values": "CloudEventData" + }, + { + "type": "array", + "items": "CloudEventData" + }, + "double", + "string" + ] + } + } + ] + }, + "double", + "string" + ] + }, + { + "type": "array", + "items": "CloudEventData" + }, + "double", + "string" + ], + "default": "null" + } + ] +} \ No newline at end of file diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java new file mode 100644 index 000000000..cfa8ee1d7 --- /dev/null +++ b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java @@ -0,0 +1,168 @@ +/* + * 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.avro; + + +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.cloudevents.SpecVersion; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.data.BytesCloudEventData; +import io.cloudevents.core.format.EventDeserializationException; +import io.cloudevents.core.format.EventFormat; +import io.cloudevents.core.format.EventSerializationException; +import io.cloudevents.core.v1.CloudEventV1; +import io.cloudevents.rw.CloudEventDataMapper; +import io.cloudevents.types.Time; +import io.cloudevents.v1.avro.CloudEvent.Builder; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; + +/** + * An implementation of {@link EventFormat} for the Avro format. + * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_CONTENT_TYPE}. + * It only supports data that is bytes. + */ +public class AvroFormat implements EventFormat { + + public static final String AVRO_CONTENT_TYPE = "application/cloudevents+avro"; + + @Override + public byte[] serialize(CloudEvent ce) throws EventSerializationException { + try { + Builder builder; + builder = io.cloudevents.v1.avro.CloudEvent.newBuilder(); + + Map attribute = new HashMap() { + @Override + public Object put(String key, Object value) { + if (value instanceof Boolean || value instanceof Integer || value instanceof String) + return super.put(key, value); + else if (value instanceof byte[]) + return super.put(key, ByteBuffer.wrap((byte[]) value)); + else + // we cannot serialize this attribute because Avro schema does not support it + throw new IllegalStateException(String.format("unsupported attribute class %s of class %s", key, value.getClass())); + } + }; + + // mandatory + attribute.put(CloudEventV1.SPECVERSION, ce.getSpecVersion().toString()); + attribute.put(CloudEventV1.SOURCE, ce.getSource().toString()); + attribute.put(CloudEventV1.TYPE, ce.getType()); + attribute.put(CloudEventV1.ID, ce.getId()); + + // optional + if (ce.getTime() != null) + attribute.put(CloudEventV1.TIME, Time.writeTime(ce.getTime())); + if (ce.getSubject() != null) + attribute.put(CloudEventV1.SUBJECT, ce.getSubject()); + if (ce.getDataContentType() != null) + attribute.put(CloudEventV1.DATACONTENTTYPE, ce.getDataContentType()); + if (ce.getDataSchema() != null) + attribute.put(CloudEventV1.DATASCHEMA, ce.getDataSchema().toString()); + + // extensions + for (String name : ce.getExtensionNames()) { + attribute.put(name, ce.getExtension(name)); + } + + builder.setAttribute(attribute); + + CloudEventData data = ce.getData(); + if (data != null) { + builder.setData(ByteBuffer.wrap(data.toBytes())); + } + return builder.build().toByteBuffer().array(); + } catch (Exception e) { + throw new EventSerializationException(e); + } + } + + @Override + public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) + throws EventDeserializationException { + try { + io.cloudevents.v1.avro.CloudEvent avroCe = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); + CloudEventBuilder builder = CloudEventBuilder.fromSpecVersion(SpecVersion.parse(avroCe.getAttribute().get(CloudEventV1.SPECVERSION).toString())); + + // attributes + extensions + for (Map.Entry entry : avroCe.getAttribute().entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + switch (name) { + case CloudEventV1.SPECVERSION: + break; + case CloudEventV1.TYPE: + builder.withSource(URI.create((String) value)); + break; + case CloudEventV1.SOURCE: + builder.withType((String) value); + break; + case CloudEventV1.ID: + builder.withId((String) value); + break; + case CloudEventV1.TIME: + builder.withTime(Time.parseTime((String) value)); + break; + case CloudEventV1.SUBJECT: + builder.withSubject((String) value); + break; + case CloudEventV1.DATACONTENTTYPE: + builder.withDataContentType((String) value); + break; + case CloudEventV1.DATASCHEMA: + builder.withDataSchema(URI.create((String) value)); + break; + default: + // must be an extension + // Avro supports boolean, int, string, bytes + if (value instanceof Boolean) + builder.withExtension(name, (boolean) value); + else if (value instanceof Integer) + builder.withExtension(name, (int) value); + else if (value instanceof String) + builder.withExtension(name, (String) value); + else if (value instanceof ByteBuffer) + builder.withExtension(name, ((ByteBuffer) value).array()); + else + // this cannot happen, if ever seen, must be bug in this library + throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass())); + } + } + + if (avroCe.getData() == null) + return builder.end(); + if (avroCe.getData() instanceof ByteBuffer) { + CloudEventData data = BytesCloudEventData.wrap(((ByteBuffer) avroCe.getData()).array()); + return builder.end(mapper.map(data)); + } else + // this will be the JSON case, we don't support this yet, because it is "bottom left quadrant", i.e. low benefit, high cost + throw new IllegalStateException(String.format("unsupported data class %s", avroCe.getData().getClass())); + } catch (Exception e) { + throw new EventDeserializationException(e); + } + } + + @Override + public String serializedContentType() { + return AVRO_CONTENT_TYPE; + } +} diff --git a/formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat b/formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat new file mode 100644 index 000000000..31cb85d6d --- /dev/null +++ b/formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat @@ -0,0 +1 @@ +io.cloudevents.avro.AvroFormat diff --git a/formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java b/formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java new file mode 100644 index 000000000..1ae284911 --- /dev/null +++ b/formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java @@ -0,0 +1,76 @@ +/* + * 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.avro; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.data.BytesCloudEventData; +import io.cloudevents.core.data.PojoCloudEventData; +import io.cloudevents.core.format.EventFormat; +import io.cloudevents.core.provider.EventFormatProvider; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.time.OffsetDateTime; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AvroFormatTest { + + private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroFormat.AVRO_CONTENT_TYPE); + + // TODO - add test cases for + // - null data + // - non-bytes data + // - extension that is bytes + // - invalid extension type + @Test + void format() { + assertNotNull(format); + assertEquals(Collections.singleton("application/cloudevents+avro"), format.deserializableContentTypes()); + + CloudEvent event = CloudEventBuilder.v1() + // mandatory + .withId("") + .withSource(URI.create("")) + .withType("") + // optional + .withTime(OffsetDateTime.MIN) + .withSubject("") + .withDataSchema(URI.create("")) + // extension + // support boolean, int, string, bytes + .withExtension("boolean", false) + .withExtension("int", 0) + .withExtension("string", "") + // omitting bytes, because it is not supported be CloudEvent.equals + .withData("", BytesCloudEventData.wrap(new byte[0])) + .build(); + + byte[] serialized = format.serialize(event); + + assertNotNull(serialized); + + CloudEvent deserialized = format.deserialize(serialized); + + assertEquals(event, deserialized); + + } +} diff --git a/pom.xml b/pom.xml index b0f706009..c927e409a 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,7 @@ api core + formats/avro formats/json-jackson formats/protobuf formats/xml From 3d055aeb948587f0e482872af46ed4cb7352a9e8 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 7 Apr 2023 10:30:57 -0700 Subject: [PATCH 02/28] docs Signed-off-by: Alex Collins --- README.md | 1 + docs/index.md | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 4f9fa58e4..30e2984e1 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Javadocs are available on [javadoc.io](https://www.javadoc.io): - [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api) - [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core) +- [cloudevents-avro](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro) - [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson) - [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf) - [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml) diff --git a/docs/index.md b/docs/index.md index d8ec2ebe9..4799df08d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,23 +27,25 @@ Using the Java SDK you can: ## Supported features | | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | -| :------------------------------------------------: | :---------------------------------------------------: | :---------------------------------------------------: | +|:--------------------------------------------------:|:-----------------------------------------------------:|:-----------------------------------------------------:| | CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: | | AMQP Protocol Binding | :x: | :x: | | - [Proton](amqp-proton.md) | :heavy_check_mark: | :heavy_check_mark: | -| AVRO Event Format | :x: | :x: | +| AVRO Event Format | :x: | :heavy_check_mark: | | HTTP Protocol Binding | :heavy_check_mark: | :heavy_check_mark: | | - [Vert.x](http-vertx.md) | :heavy_check_mark: | :heavy_check_mark: | | - [Jakarta Restful WS](http-jakarta-restful-ws.md) | :heavy_check_mark: | :heavy_check_mark: | | - [Basic](http-basic.md) | :heavy_check_mark: | :heavy_check_mark: | | - [Spring](spring.md) | :heavy_check_mark: | :heavy_check_mark: | -| - [http4k][http4k] | :heavy_check_mark: | :heavy_check_mark: | +| - [http4k][http4k] | :heavy_check_mark: | :heavy_check_mark: | | JSON Event Format | :heavy_check_mark: | :heavy_check_mark: | +| - [Avro](avro.md) | :x: | :heavy_check_mark: | +| Avro Event Format | :x: | :heavy_check_mark: | | - [Jackson](json-jackson.md) | :heavy_check_mark: | :heavy_check_mark: | -| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: | -| - [Proto](protobuf.md) | :heavy_check_mark: | :heavy_check_mark: | -| XML Event Format | :heavy_check_mark: | :heavy_check_mark: | -| - [XML](xml.md) | :heavy_check_mark: | :heavy_check_mark: | +| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: | +| - [Proto](protobuf.md) | :heavy_check_mark: | :heavy_check_mark: | +| XML Event Format | :heavy_check_mark: | :heavy_check_mark: | +| - [XML](xml.md) | :heavy_check_mark: | :heavy_check_mark: | | [Kafka Protocol Binding](kafka.md) | :heavy_check_mark: | :heavy_check_mark: | | MQTT Protocol Binding | :x: | :x: | | NATS Protocol Binding | :x: | :x: | @@ -94,6 +96,8 @@ a different feature from the different sub specs of - [`cloudevents-bom`] Module providing a [bill of materials (BOM)](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#bill-of-materials-bom-poms) for easier integration of CloudEvents in other projects +- [`cloudevents-avro`] Implementation of [Avro Event format] using + [Apache Avro](https://avro.apache.org/) - [`cloudevents-json-jackson`] Implementation of [JSON Event format] with [Jackson](https://github.com/FasterXML/jackson) - [`cloudevents-protobuf`] Implementation of [Protobuf Event format] using code generated @@ -116,6 +120,7 @@ a different feature from the different sub specs of You can look at the latest published artifacts on [Maven Central](https://search.maven.org/search?q=g:io.cloudevents). +[Avro Event format]: https://github.com/cloudevents/spec/blob/main/avro-format.md [JSON Event format]: https://github.com/cloudevents/spec/blob/v1.0/json-format.md [Protobuf Event format]: https://github.com/cloudevents/spec/blob/v1.0.1/protobuf-format.md [HTTP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md @@ -124,6 +129,7 @@ You can look at the latest published artifacts on [`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/main/api [`cloudevents-bom`]: https://github.com/cloudevents/sdk-java/tree/main/bom [`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/main/core +[`cloudevents-avro`]: https://github.com/cloudevents/sdk-java/tree/main/formats/avro [`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/main/formats/json-jackson [`cloudevents-protobuf`]: https://github.com/cloudevents/sdk-java/tree/main/formats/protobuf [`cloudevents-xml`]: https://github.com/cloudevents/sdk-java/tree/main/formats/xml From ad728dd8c5f5c959bf3b7a0ba1947b1e71564f4d Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 7 Apr 2023 10:33:07 -0700 Subject: [PATCH 03/28] docs Signed-off-by: Alex Collins --- docs/avro.md | 51 +++------------------------------------------------ 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/docs/avro.md b/docs/avro.md index 203cab0ca..c9920dff5 100644 --- a/docs/avro.md +++ b/docs/avro.md @@ -9,7 +9,7 @@ nav_order: 4 This module provides the Avro Buffer (avro) `EventFormat` implementation using the Java avro runtime and classes generated from the CloudEvents -[avro spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.avro). +[avro spec](https://github.com/cloudevents/spec/blob/main/spec.avro). # Setup For Maven based projects, use the following dependency: @@ -24,7 +24,7 @@ For Maven based projects, use the following dependency: No further configuration is required is use the module. -## Using the avro Event Format +## Using the Avro Event Format ### Event serialization @@ -42,55 +42,10 @@ CloudEvent event = CloudEventBuilder.v1() byte[]serialized = EventFormatProvider .getInstance() - .resolveFormat(avroFormat.CONTENT_TYPE) + .resolveFormat(AvroFormat.CONTENT_TYPE) .serialize(event); ``` The `EventFormatProvider` will automatically resolve the `avroFormat` using the `ServiceLoader` APIs. -## Passing avro messages as CloudEvent data. - -The `AvroCloudEventData` capability provides a convenience mechanism to handle avro message object data. - -### Building - -```java -// Build my business event message. -com.google.avro.Message myMessage = ..... ; - -// Wrap the avro message as CloudEventData. -CloudEventData ceData = AvroCloudEventData.wrap(myMessage); - -// Build the CloudEvent -CloudEvent event = CloudEventBuilder.v1() - .withId("hello") - .withType("example.avrodata") - .withSource(URI.create("http://localhost")) - .withData(ceData) - .build(); -``` - -### Reading - -If the `AvroFormat` is used to deserialize a CloudEvent that contains a avro message object as data you can use -the `AvroCloudEventData` to access it as an 'Any' directly. - -```java - -// Deserialize the event. -CloudEvent myEvent = eventFormat.deserialize(raw); - -// Get the Data -CloudEventData eventData = myEvent.getData(); - -if (ceData instanceOf AvroCloudEventData) { - - // Obtain the avro 'any' - Any anAny = ((AvroCloudEventData) eventData).getAny(); - - ... -} - -``` - From 80b4de784c88e651759f11546631cb87c3e434fe Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Thu, 13 Apr 2023 18:01:30 +0000 Subject: [PATCH 04/28] fix: simpler Signed-off-by: Alex Collins --- .../java/io/cloudevents/avro/AvroFormat.java | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java index cfa8ee1d7..1e91fb286 100644 --- a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java +++ b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java @@ -47,21 +47,9 @@ public class AvroFormat implements EventFormat { @Override public byte[] serialize(CloudEvent ce) throws EventSerializationException { try { - Builder builder; - builder = io.cloudevents.v1.avro.CloudEvent.newBuilder(); - - Map attribute = new HashMap() { - @Override - public Object put(String key, Object value) { - if (value instanceof Boolean || value instanceof Integer || value instanceof String) - return super.put(key, value); - else if (value instanceof byte[]) - return super.put(key, ByteBuffer.wrap((byte[]) value)); - else - // we cannot serialize this attribute because Avro schema does not support it - throw new IllegalStateException(String.format("unsupported attribute class %s of class %s", key, value.getClass())); - } - }; + Builder builder = io.cloudevents.v1.avro.CloudEvent.newBuilder(); + + Map attribute = new HashMap<>(); // mandatory attribute.put(CloudEventV1.SPECVERSION, ce.getSpecVersion().toString()); @@ -81,7 +69,11 @@ else if (value instanceof byte[]) // extensions for (String name : ce.getExtensionNames()) { - attribute.put(name, ce.getExtension(name)); + Object value = ce.getExtension(name); + if (value instanceof byte[]) + attribute.put(name, ByteBuffer.wrap((byte[]) value)); + else + attribute.put(name, value); } builder.setAttribute(attribute); @@ -138,7 +130,7 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper Date: Thu, 13 Apr 2023 18:24:12 +0000 Subject: [PATCH 05/28] use fields Signed-off-by: Alex Collins --- formats/avro/src/main/avro/cloudevents.avsc | 28 +++++++ .../java/io/cloudevents/avro/AvroFormat.java | 81 +++++++------------ 2 files changed, 59 insertions(+), 50 deletions(-) diff --git a/formats/avro/src/main/avro/cloudevents.avsc b/formats/avro/src/main/avro/cloudevents.avsc index 4eb2d705c..9574d8f02 100644 --- a/formats/avro/src/main/avro/cloudevents.avsc +++ b/formats/avro/src/main/avro/cloudevents.avsc @@ -5,6 +5,34 @@ "version": "1.0", "doc": "Avro Event Format for CloudEvents", "fields": [ + { + "name": "id", + "type": "string" + }, + { + "name": "source", + "type": "string" + }, + { + "name": "type", + "type": "string" + }, + { + "name": "datacontenttype", + "type": ["null", "string"] + }, + { + "name": "dataschema", + "type": ["null", "string"] + }, + { + "name": "subject", + "type": ["null", "string"] + }, + { + "name": "time", + "type": ["null", "string"] + }, { "name": "attribute", "type": { diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java index 1e91fb286..77af7c14d 100644 --- a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java +++ b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java @@ -49,25 +49,23 @@ public byte[] serialize(CloudEvent ce) throws EventSerializationException { try { Builder builder = io.cloudevents.v1.avro.CloudEvent.newBuilder(); - Map attribute = new HashMap<>(); - // mandatory - attribute.put(CloudEventV1.SPECVERSION, ce.getSpecVersion().toString()); - attribute.put(CloudEventV1.SOURCE, ce.getSource().toString()); - attribute.put(CloudEventV1.TYPE, ce.getType()); - attribute.put(CloudEventV1.ID, ce.getId()); + builder.setSource(ce.getSource().toString()); + builder.setType(ce.getType()); + builder.setId(ce.getId()); // optional if (ce.getTime() != null) - attribute.put(CloudEventV1.TIME, Time.writeTime(ce.getTime())); + builder.setTime(Time.writeTime(ce.getTime())); if (ce.getSubject() != null) - attribute.put(CloudEventV1.SUBJECT, ce.getSubject()); + builder.setSubject(ce.getSubject()); if (ce.getDataContentType() != null) - attribute.put(CloudEventV1.DATACONTENTTYPE, ce.getDataContentType()); + builder.setDatacontenttype(ce.getDataContentType()); if (ce.getDataSchema() != null) - attribute.put(CloudEventV1.DATASCHEMA, ce.getDataSchema().toString()); + builder.setDataschema(ce.getDataSchema().toString()); // extensions + Map attribute = new HashMap<>(); for (String name : ce.getExtensionNames()) { Object value = ce.getExtension(name); if (value instanceof byte[]) @@ -93,51 +91,34 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper entry : avroCe.getAttribute().entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); - switch (name) { - case CloudEventV1.SPECVERSION: - break; - case CloudEventV1.TYPE: - builder.withSource(URI.create((String) value)); - break; - case CloudEventV1.SOURCE: - builder.withType((String) value); - break; - case CloudEventV1.ID: - builder.withId((String) value); - break; - case CloudEventV1.TIME: - builder.withTime(Time.parseTime((String) value)); - break; - case CloudEventV1.SUBJECT: - builder.withSubject((String) value); - break; - case CloudEventV1.DATACONTENTTYPE: - builder.withDataContentType((String) value); - break; - case CloudEventV1.DATASCHEMA: - builder.withDataSchema(URI.create((String) value)); - break; - default: - // must be an extension - // Avro supports boolean, int, string, bytes - if (value instanceof Boolean) - builder.withExtension(name, (boolean) value); - else if (value instanceof Integer) - builder.withExtension(name, (int) value); - else if (value instanceof String) - builder.withExtension(name, (String) value); - else if (value instanceof ByteBuffer) - builder.withExtension(name, ((ByteBuffer) value).array()); - else - // this cannot happen, if ever seen, must be bug in this library - throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass())); - } + // Avro supports boolean, int, string, bytes + if (value instanceof Boolean) + builder.withExtension(name, (boolean) value); + else if (value instanceof Integer) + builder.withExtension(name, (int) value); + else if (value instanceof String) + builder.withExtension(name, (String) value); + else if (value instanceof ByteBuffer) + builder.withExtension(name, ((ByteBuffer) value).array()); + else + // this cannot happen, if ever seen, must be bug in this library + throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass())); } if (avroCe.getData() == null) From 3338bf64e1ac520f7d5a5bd522bd1c7fc52027b3 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 18 Apr 2023 14:03:50 -0700 Subject: [PATCH 06/28] fix: tidy Signed-off-by: Alex Collins --- .../java/io/cloudevents/avro/AvroFormat.java | 50 ++++++++----------- 1 file changed, 20 insertions(+), 30 deletions(-) diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java index 77af7c14d..0c8c31ce9 100644 --- a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java +++ b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java @@ -49,37 +49,28 @@ public byte[] serialize(CloudEvent ce) throws EventSerializationException { try { Builder builder = io.cloudevents.v1.avro.CloudEvent.newBuilder(); - // mandatory - builder.setSource(ce.getSource().toString()); - builder.setType(ce.getType()); - builder.setId(ce.getId()); - - // optional - if (ce.getTime() != null) - builder.setTime(Time.writeTime(ce.getTime())); - if (ce.getSubject() != null) - builder.setSubject(ce.getSubject()); - if (ce.getDataContentType() != null) - builder.setDatacontenttype(ce.getDataContentType()); - if (ce.getDataSchema() != null) - builder.setDataschema(ce.getDataSchema().toString()); - // extensions Map attribute = new HashMap<>(); for (String name : ce.getExtensionNames()) { Object value = ce.getExtension(name); - if (value instanceof byte[]) - attribute.put(name, ByteBuffer.wrap((byte[]) value)); - else - attribute.put(name, value); + attribute.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); } - builder.setAttribute(attribute); + builder.setSource(ce.getSource().toString()) + .setType(ce.getType()) + .setId(ce.getId()) + .setSubject(ce.getSubject()) + .setDatacontenttype(ce.getDataContentType()) + .setAttribute(attribute); + + if (ce.getTime() != null) + builder.setTime(Time.writeTime(ce.getTime())); + if (ce.getDataSchema() != null) + builder.setDataschema(ce.getDataSchema().toString()); CloudEventData data = ce.getData(); - if (data != null) { + if (data != null) builder.setData(ByteBuffer.wrap(data.toBytes())); - } return builder.build().toByteBuffer().array(); } catch (Exception e) { throw new EventSerializationException(e); @@ -87,23 +78,22 @@ public byte[] serialize(CloudEvent ce) throws EventSerializationException { } @Override - public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) - throws EventDeserializationException { + public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) throws EventDeserializationException { try { io.cloudevents.v1.avro.CloudEvent avroCe = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); CloudEventBuilder builder = CloudEventBuilder.v1() - .withSource(URI.create(avroCe.getSource())) - .withType(avroCe.getType()) - .withId(avroCe.getType()) - .withSubject(avroCe.getSubject()) - .withDataContentType(avroCe.getDatacontenttype()); + .withSource(URI.create(avroCe.getSource())) + .withType(avroCe.getType()) + .withId(avroCe.getType()) + .withSubject(avroCe.getSubject()) + .withDataContentType(avroCe.getDatacontenttype()); if (avroCe.getTime() != null) builder.withTime(Time.parseTime(avroCe.getTime())); if (avroCe.getDataschema() != null) builder.withDataSchema(URI.create(avroCe.getDataschema())); - // attributes + extensions + // extensions for (Map.Entry entry : avroCe.getAttribute().entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); From 6c30e27d144b48d040bbbbc67e40af2f6fc5616c Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Thu, 20 Apr 2023 09:59:03 -0700 Subject: [PATCH 07/28] fix: change to long Signed-off-by: Alex Collins --- formats/avro/src/main/avro/cloudevents.avsc | 26 +++++++++++++++---- .../java/io/cloudevents/avro/AvroFormat.java | 6 +++-- .../io/cloudevents/avro/AvroFormatTest.java | 4 ++- 3 files changed, 28 insertions(+), 8 deletions(-) diff --git a/formats/avro/src/main/avro/cloudevents.avsc b/formats/avro/src/main/avro/cloudevents.avsc index 9574d8f02..78bacfd29 100644 --- a/formats/avro/src/main/avro/cloudevents.avsc +++ b/formats/avro/src/main/avro/cloudevents.avsc @@ -5,7 +5,7 @@ "version": "1.0", "doc": "Avro Event Format for CloudEvents", "fields": [ - { + { "name": "id", "type": "string" }, @@ -19,19 +19,35 @@ }, { "name": "datacontenttype", - "type": ["null", "string"] + "type": [ + "null", + "string" + ], + "default": null }, { "name": "dataschema", - "type": ["null", "string"] + "type": [ + "null", + "string" + ], + "default": null }, { "name": "subject", - "type": ["null", "string"] + "type": [ + "null", + "string" + ], + "default": null }, { "name": "time", - "type": ["null", "string"] + "type": [ + "null", + "long" + ], + "default": null }, { "name": "attribute", diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java index 0c8c31ce9..5e796e259 100644 --- a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java +++ b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java @@ -32,6 +32,8 @@ import java.net.URI; import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.ZoneOffset; import java.util.HashMap; import java.util.Map; @@ -64,7 +66,7 @@ public byte[] serialize(CloudEvent ce) throws EventSerializationException { .setAttribute(attribute); if (ce.getTime() != null) - builder.setTime(Time.writeTime(ce.getTime())); + builder.setTime(ce.getTime().toInstant().toEpochMilli()); if (ce.getDataSchema() != null) builder.setDataschema(ce.getDataSchema().toString()); @@ -89,7 +91,7 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper Date: Thu, 20 Apr 2023 12:05:55 -0700 Subject: [PATCH 08/28] fix: simplify Signed-off-by: Alex Collins --- formats/avro/src/main/avro/cloudevents.avsc | 53 ++---------- .../java/io/cloudevents/avro/AvroFormat.java | 82 +++++++++---------- 2 files changed, 45 insertions(+), 90 deletions(-) diff --git a/formats/avro/src/main/avro/cloudevents.avsc b/formats/avro/src/main/avro/cloudevents.avsc index 78bacfd29..53bf74884 100644 --- a/formats/avro/src/main/avro/cloudevents.avsc +++ b/formats/avro/src/main/avro/cloudevents.avsc @@ -45,7 +45,10 @@ "name": "time", "type": [ "null", - "long" + { + "type": "long", + "logicalType": "timestamp-millis" + } ], "default": null }, @@ -60,56 +63,14 @@ "string", "bytes" ] - } + }, + "default": {} }, { "name": "data", "type": [ "bytes", - "null", - "boolean", - { - "type": "map", - "values": [ - "null", - "boolean", - { - "type": "record", - "name": "CloudEventData", - "doc": "Representation of a JSON Value", - "fields": [ - { - "name": "value", - "type": { - "type": "map", - "values": [ - "null", - "boolean", - { - "type": "map", - "values": "CloudEventData" - }, - { - "type": "array", - "items": "CloudEventData" - }, - "double", - "string" - ] - } - } - ] - }, - "double", - "string" - ] - }, - { - "type": "array", - "items": "CloudEventData" - }, - "double", - "string" + "null" ], "default": "null" } diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java index 5e796e259..f910f4156 100644 --- a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java +++ b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java @@ -19,20 +19,16 @@ import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; -import io.cloudevents.SpecVersion; import io.cloudevents.core.builder.CloudEventBuilder; import io.cloudevents.core.data.BytesCloudEventData; import io.cloudevents.core.format.EventDeserializationException; import io.cloudevents.core.format.EventFormat; import io.cloudevents.core.format.EventSerializationException; -import io.cloudevents.core.v1.CloudEventV1; import io.cloudevents.rw.CloudEventDataMapper; -import io.cloudevents.types.Time; import io.cloudevents.v1.avro.CloudEvent.Builder; import java.net.URI; import java.nio.ByteBuffer; -import java.time.Instant; import java.time.ZoneOffset; import java.util.HashMap; import java.util.Map; @@ -47,33 +43,33 @@ public class AvroFormat implements EventFormat { public static final String AVRO_CONTENT_TYPE = "application/cloudevents+avro"; @Override - public byte[] serialize(CloudEvent ce) throws EventSerializationException { + public byte[] serialize(CloudEvent from) throws EventSerializationException { try { - Builder builder = io.cloudevents.v1.avro.CloudEvent.newBuilder(); + Builder to = io.cloudevents.v1.avro.CloudEvent.newBuilder(); // extensions Map attribute = new HashMap<>(); - for (String name : ce.getExtensionNames()) { - Object value = ce.getExtension(name); + for (String name : from.getExtensionNames()) { + Object value = from.getExtension(name); attribute.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); } - builder.setSource(ce.getSource().toString()) - .setType(ce.getType()) - .setId(ce.getId()) - .setSubject(ce.getSubject()) - .setDatacontenttype(ce.getDataContentType()) + to.setSource(from.getSource().toString()) + .setType(from.getType()) + .setId(from.getId()) + .setSubject(from.getSubject()) + .setDatacontenttype(from.getDataContentType()) .setAttribute(attribute); - if (ce.getTime() != null) - builder.setTime(ce.getTime().toInstant().toEpochMilli()); - if (ce.getDataSchema() != null) - builder.setDataschema(ce.getDataSchema().toString()); + if (from.getTime() != null) + to.setTime(from.getTime().toInstant()); + if (from.getDataSchema() != null) + to.setDataschema(from.getDataSchema().toString()); - CloudEventData data = ce.getData(); + CloudEventData data = from.getData(); if (data != null) - builder.setData(ByteBuffer.wrap(data.toBytes())); - return builder.build().toByteBuffer().array(); + to.setData(ByteBuffer.wrap(data.toBytes())); + return to.build().toByteBuffer().array(); } catch (Exception e) { throw new EventSerializationException(e); } @@ -82,45 +78,43 @@ public byte[] serialize(CloudEvent ce) throws EventSerializationException { @Override public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) throws EventDeserializationException { try { - io.cloudevents.v1.avro.CloudEvent avroCe = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); - CloudEventBuilder builder = CloudEventBuilder.v1() - .withSource(URI.create(avroCe.getSource())) - .withType(avroCe.getType()) - .withId(avroCe.getType()) - .withSubject(avroCe.getSubject()) - .withDataContentType(avroCe.getDatacontenttype()); + io.cloudevents.v1.avro.CloudEvent from = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); + CloudEventBuilder to = CloudEventBuilder.v1() + .withSource(URI.create(from.getSource())) + .withType(from.getType()) + .withId(from.getType()) + .withSubject(from.getSubject()) + .withDataContentType(from.getDatacontenttype()); - if (avroCe.getTime() != null) - builder.withTime(Instant.ofEpochMilli(avroCe.getTime()).atOffset(ZoneOffset.UTC)); - if (avroCe.getDataschema() != null) - builder.withDataSchema(URI.create(avroCe.getDataschema())); + if (from.getTime() != null) + to.withTime(from.getTime().atOffset(ZoneOffset.UTC)); + if (from.getDataschema() != null) + to.withDataSchema(URI.create(from.getDataschema())); // extensions - for (Map.Entry entry : avroCe.getAttribute().entrySet()) { + for (Map.Entry entry : from.getAttribute().entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); // Avro supports boolean, int, string, bytes if (value instanceof Boolean) - builder.withExtension(name, (boolean) value); + to.withExtension(name, (boolean) value); else if (value instanceof Integer) - builder.withExtension(name, (int) value); + to.withExtension(name, (int) value); else if (value instanceof String) - builder.withExtension(name, (String) value); + to.withExtension(name, (String) value); else if (value instanceof ByteBuffer) - builder.withExtension(name, ((ByteBuffer) value).array()); + to.withExtension(name, ((ByteBuffer) value).array()); else // this cannot happen, if ever seen, must be bug in this library throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass())); } - if (avroCe.getData() == null) - return builder.end(); - if (avroCe.getData() instanceof ByteBuffer) { - CloudEventData data = BytesCloudEventData.wrap(((ByteBuffer) avroCe.getData()).array()); - return builder.end(mapper.map(data)); - } else - // this will be the JSON case, we don't support this yet, because it is "bottom left quadrant", i.e. low benefit, high cost - throw new IllegalStateException(String.format("unsupported data class %s", avroCe.getData().getClass())); + if (from.getData() == null) + return to.end(); + else { + CloudEventData data = BytesCloudEventData.wrap(from.getData().array()); + return to.end(mapper.map(data)); + } } catch (Exception e) { throw new EventDeserializationException(e); } From 9e0a4c71137d563cfecb537e06edb59cd492c6d2 Mon Sep 17 00:00:00 2001 From: skepticoitusInteruptus <97033958+skepticoitusInteruptus@users.noreply.github.com> Date: Fri, 21 Apr 2023 08:41:10 +0000 Subject: [PATCH 09/28] Refactor to Facilitate Decoupling from Concrete Implementations of EventFormat (#539) - Introduce ContentType enum - Resolve formats by using the ContentType enum Signed-off-by: Randi Sheaffer-Klass <97033958+skepticoitusInteruptus@users.noreply.github.com> Signed-off-by: Alex Collins --- .../cloudevents/core/format/ContentType.java | 68 +++++++++++++++++++ .../core/provider/EventFormatProvider.java | 11 +++ docs/json-jackson.md | 4 +- docs/protobuf.md | 4 +- docs/xml.md | 4 +- .../io/cloudevents/jackson/JsonFormat.java | 1 + .../cloudevents/jackson/JsonFormatTest.java | 9 ++- .../cloudevents/protobuf/ProtobufFormat.java | 1 + .../java/io/cloudevents/xml/XMLFormat.java | 1 + 9 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/io/cloudevents/core/format/ContentType.java diff --git a/core/src/main/java/io/cloudevents/core/format/ContentType.java b/core/src/main/java/io/cloudevents/core/format/ContentType.java new file mode 100644 index 000000000..1d8656393 --- /dev/null +++ b/core/src/main/java/io/cloudevents/core/format/ContentType.java @@ -0,0 +1,68 @@ +/* + * 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.core.format; + +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.cloudevents.rw.CloudEventDataMapper; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Collections; +import java.util.Set; + +/** + *

A construct that aggregates a two-part identifier of file formats and format contents transmitted on the Internet. + * + *

The two parts of a {@code ContentType} are its type and a subtype; separated by a forward slash ({@code /}). + * + *

The constants enumerated by {@code ContentType} correspond only to the specialized formats supported by the Java™ SDK for CloudEvents. + * + * @see io.cloudevents.core.format.EventFormat + */ +@ParametersAreNonnullByDefault +public enum ContentType { + + /** + * Content type associated with the JSON event format + */ + JSON("application/cloudevents+json"), + /** + * The content type for transports sending cloudevents in the protocol buffer format. + */ + PROTO("application/cloudevents+protobuf"), + /** + * The content type for transports sending cloudevents in XML format. + */ + XML("application/cloudevents+xml"); + + private String value; + + private ContentType(String value) { this.value = value; } + + /** + * Return a string consisting of the slash-delimited ({@code /}) two-part identifier for this {@code enum} constant. + */ + public String value() { return value; } + + /** + * Return a string consisting of the slash-delimited ({@code /}) two-part identifier for this {@code enum} constant. + */ + @Override + public String toString() { return value(); } + +} diff --git a/core/src/main/java/io/cloudevents/core/provider/EventFormatProvider.java b/core/src/main/java/io/cloudevents/core/provider/EventFormatProvider.java index 96f3ad678..d961d2f85 100644 --- a/core/src/main/java/io/cloudevents/core/provider/EventFormatProvider.java +++ b/core/src/main/java/io/cloudevents/core/provider/EventFormatProvider.java @@ -25,6 +25,7 @@ import javax.annotation.ParametersAreNonnullByDefault; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventFormat; import io.cloudevents.lang.Nullable; @@ -98,4 +99,14 @@ public EventFormat resolveFormat(String contentType) { return this.formats.get(contentType); } + /** + * Resolve an event format starting from the content type. + * + * @param contentType the content type to resolve the event format + * @return null if no format was found for the provided content type + */ + @Nullable + public EventFormat resolveFormat(ContentType contentType) { + return this.formats.get(contentType.value()); + } } diff --git a/docs/json-jackson.md b/docs/json-jackson.md index 6bb2a7c6a..823436f02 100644 --- a/docs/json-jackson.md +++ b/docs/json-jackson.md @@ -28,9 +28,9 @@ adding the dependency to your project: ```java import io.cloudevents.CloudEvent; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventFormatProvider; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.jackson.JsonFormat; CloudEvent event = CloudEventBuilder.v1() .withId("hello") @@ -40,7 +40,7 @@ CloudEvent event = CloudEventBuilder.v1() byte[]serialized = EventFormatProvider .getInstance() - .resolveFormat(JsonFormat.CONTENT_TYPE) + .resolveFormat(ContentType.JSON) .serialize(event); ``` diff --git a/docs/protobuf.md b/docs/protobuf.md index d67a0d708..6a49ec86b 100644 --- a/docs/protobuf.md +++ b/docs/protobuf.md @@ -30,9 +30,9 @@ No further configuration is required is use the module. ```java import io.cloudevents.CloudEvent; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventFormatProvider; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.protobuf.ProtobufFormat; CloudEvent event = CloudEventBuilder.v1() .withId("hello") @@ -42,7 +42,7 @@ CloudEvent event = CloudEventBuilder.v1() byte[]serialized = EventFormatProvider .getInstance() - .resolveFormat(ProtobufFormat.CONTENT_TYPE) + .resolveFormat(ContentType.PROTO) .serialize(event); ``` diff --git a/docs/xml.md b/docs/xml.md index 6d0640157..8e365ac1f 100644 --- a/docs/xml.md +++ b/docs/xml.md @@ -29,9 +29,9 @@ adding the dependency to your project: ```java import io.cloudevents.CloudEvent; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventFormatProvider; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.xml.XMLFormat; CloudEvent event = CloudEventBuilder.v1() .withId("hello") @@ -41,7 +41,7 @@ CloudEvent event = CloudEventBuilder.v1() byte[] serialized = EventFormatProvider .getInstance() - .resolveFormat(XMLFormat.CONTENT_TYPE) + .resolveFormat(ContentType.XML) .serialize(event); ``` 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 542405894..d73edbdf9 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 @@ -22,6 +22,7 @@ import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventDeserializationException; import io.cloudevents.core.format.EventFormat; import io.cloudevents.core.format.EventSerializationException; 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 f82d25926..59c2d70c0 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 @@ -24,6 +24,7 @@ import io.cloudevents.CloudEvent; import io.cloudevents.SpecVersion; import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventDeserializationException; import io.cloudevents.core.provider.EventFormatProvider; import io.cloudevents.rw.CloudEventRWException; @@ -40,6 +41,7 @@ import java.util.Objects; import java.util.stream.Stream; +import static io.cloudevents.core.format.ContentType.*; import static io.cloudevents.core.test.Data.*; import static org.assertj.core.api.Assertions.*; @@ -185,7 +187,10 @@ static Stream jsonContentTypes() { //https://www.rfc-editor.org/rfc/rfc2045#section-5.1 // any us-ascii char can be part of parameters (except CTRLs and tspecials) Arguments.of("text/json; char-set = $!#$%&'*+.^_`|"), - Arguments.of((Object) null) + Arguments.of((Object) null), + Arguments.of(JSON + ""), + Arguments.of(JSON.value()), + Arguments.of(JSON.toString()) ); } @@ -307,7 +312,7 @@ public static Stream badJsonContent() { } private JsonFormat getFormat() { - return (JsonFormat) EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE); + return (JsonFormat) EventFormatProvider.getInstance().resolveFormat(JSON); } private static byte[] loadFile(String input) { diff --git a/formats/protobuf/src/main/java/io/cloudevents/protobuf/ProtobufFormat.java b/formats/protobuf/src/main/java/io/cloudevents/protobuf/ProtobufFormat.java index 0d3b65d4e..170d92f20 100644 --- a/formats/protobuf/src/main/java/io/cloudevents/protobuf/ProtobufFormat.java +++ b/formats/protobuf/src/main/java/io/cloudevents/protobuf/ProtobufFormat.java @@ -20,6 +20,7 @@ import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventDeserializationException; import io.cloudevents.core.format.EventFormat; import io.cloudevents.core.format.EventSerializationException; diff --git a/formats/xml/src/main/java/io/cloudevents/xml/XMLFormat.java b/formats/xml/src/main/java/io/cloudevents/xml/XMLFormat.java index 996f07cf3..d0e4c0bc7 100644 --- a/formats/xml/src/main/java/io/cloudevents/xml/XMLFormat.java +++ b/formats/xml/src/main/java/io/cloudevents/xml/XMLFormat.java @@ -19,6 +19,7 @@ import io.cloudevents.CloudEvent; import io.cloudevents.CloudEventData; import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.format.ContentType; import io.cloudevents.core.format.EventDeserializationException; import io.cloudevents.core.format.EventFormat; import io.cloudevents.core.format.EventSerializationException; From a18e04d0fb68cb5a974f6d56e17241b7fb77e9b9 Mon Sep 17 00:00:00 2001 From: Aaron Ai Date: Tue, 23 May 2023 16:49:08 +0800 Subject: [PATCH 10/28] feat: add rocketmq binding (#554) Spec details: https://github.com/apache/rocketmq-externals/blob/a6978cf562db9aab00062704dc15bddf947df9bc/rocketmq-cloudevents-binding/rocketmq-transport-binding.md Signed-off-by: Aaron Ai Signed-off-by: Alex Collins --- examples/pom.xml | 1 + examples/rocketmq/README.md | 27 ++ examples/rocketmq/pom.xml | 21 ++ .../examples/rocketmq/RocketmqConsumer.java | 50 ++++ .../examples/rocketmq/RocketmqProducer.java | 64 +++++ pom.xml | 1 + rocketmq/pom.xml | 73 ++++++ .../rocketmq/RocketMqMessageFactory.java | 80 ++++++ .../rocketmq/RocketmqBinaryMessageReader.java | 107 ++++++++ .../rocketmq/RocketmqConstants.java | 42 ++++ .../rocketmq/RocketmqMessageWriter.java | 98 ++++++++ .../rocketmq/RocketmqMessageFactoryTest.java | 233 ++++++++++++++++++ .../rocketmq/RocketmqMessageWriterTest.java | 117 +++++++++ 13 files changed, 914 insertions(+) create mode 100644 examples/rocketmq/README.md create mode 100644 examples/rocketmq/pom.xml create mode 100644 examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqConsumer.java create mode 100644 examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqProducer.java create mode 100644 rocketmq/pom.xml create mode 100644 rocketmq/src/main/java/io/cloudevents/rocketmq/RocketMqMessageFactory.java create mode 100644 rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqBinaryMessageReader.java create mode 100644 rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqConstants.java create mode 100644 rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqMessageWriter.java create mode 100644 rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageFactoryTest.java create mode 100644 rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageWriterTest.java diff --git a/examples/pom.xml b/examples/pom.xml index a706a51e2..7a8bd1538 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -29,6 +29,7 @@ spring-reactive spring-rsocket spring-function + rocketmq diff --git a/examples/rocketmq/README.md b/examples/rocketmq/README.md new file mode 100644 index 000000000..8e4a98e60 --- /dev/null +++ b/examples/rocketmq/README.md @@ -0,0 +1,27 @@ +# RocketMQ + CloudEvents Sample + +This example demonstrates the integration of [RocketMQ 5.x client library](https://github.com/apache/rocketmq-clients) +with CloudEvents to create a RocketMQ binding. + +## Building the Project + +```shell +mvn package +``` + +## Setting Up a RocketMQ Instance + +Follow the [quickstart guide](https://rocketmq.apache.org/docs/quick-start/01quickstart) on the official RocketMQ +website to set up the necessary components, including nameserver, proxy, and broker. + +## Event Production + +```shell +mvn exec:java -Dexec.mainClass="io.cloudevents.examples.rocketmq.RocketmqProducer" -Dexec.args="foobar:8081 sample-topic" +``` + +## Event Consumption + +```shell +mvn exec:java -Dexec.mainClass="io.cloudevents.examples.rocketmq.RocketmqConsumer" -Dexec.args="foobar:8081 sample-topic sample-consumer-group" +``` diff --git a/examples/rocketmq/pom.xml b/examples/rocketmq/pom.xml new file mode 100644 index 000000000..c40f24a6a --- /dev/null +++ b/examples/rocketmq/pom.xml @@ -0,0 +1,21 @@ + + + + cloudevents-examples + io.cloudevents + 2.5.0-SNAPSHOT + + 4.0.0 + + cloudevents-rocketmq-example + + + + io.cloudevents + cloudevents-rocketmq + ${project.version} + + + diff --git a/examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqConsumer.java b/examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqConsumer.java new file mode 100644 index 000000000..3f539b4ac --- /dev/null +++ b/examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqConsumer.java @@ -0,0 +1,50 @@ +package io.cloudevents.examples.rocketmq; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.message.MessageReader; +import io.cloudevents.rocketmq.RocketMqMessageFactory; +import java.io.IOException; +import java.util.Collections; +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientException; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.consumer.ConsumeResult; +import org.apache.rocketmq.client.apis.consumer.FilterExpression; +import org.apache.rocketmq.client.apis.consumer.PushConsumer; + +public class RocketmqConsumer { + private RocketmqConsumer() { + } + + public static void main(String[] args) throws InterruptedException, ClientException, IOException { + if (args.length < 3) { + System.out.println("Usage: rocketmq_consumer "); + return; + } + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + String endpoints = args[0]; + ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder() + .setEndpoints(endpoints) + .build(); + FilterExpression filterExpression = new FilterExpression(); + String topic = args[1]; + String consumerGroup = args[2]; + + // Create the RocketMQ Consumer. + PushConsumer pushConsumer = provider.newPushConsumerBuilder() + .setClientConfiguration(clientConfiguration) + .setConsumerGroup(consumerGroup) + .setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression)) + .setMessageListener(messageView -> { + final MessageReader reader = RocketMqMessageFactory.createReader(messageView); + final CloudEvent event = reader.toEvent(); + System.out.println("Received event=" + event + ", messageId=" + messageView.getMessageId()); + return ConsumeResult.SUCCESS; + }) + .build(); + // Block the main thread, no need for production environment. + Thread.sleep(Long.MAX_VALUE); + // Close the push consumer when you don't need it anymore. + pushConsumer.close(); + } +} diff --git a/examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqProducer.java b/examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqProducer.java new file mode 100644 index 000000000..adeadb7f7 --- /dev/null +++ b/examples/rocketmq/src/main/java/io/cloudevents/examples/rocketmq/RocketmqProducer.java @@ -0,0 +1,64 @@ +package io.cloudevents.examples.rocketmq; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.v1.CloudEventBuilder; +import io.cloudevents.rocketmq.RocketMqMessageFactory; +import io.cloudevents.types.Time; +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import org.apache.rocketmq.client.apis.ClientConfiguration; +import org.apache.rocketmq.client.apis.ClientException; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.message.Message; +import org.apache.rocketmq.client.apis.producer.Producer; +import org.apache.rocketmq.client.apis.producer.SendReceipt; +import org.apache.rocketmq.shaded.com.google.gson.Gson; + +public class RocketmqProducer { + private RocketmqProducer() { + } + + public static void main(String[] args) throws ClientException, IOException { + if (args.length < 2) { + System.out.println("Usage: rocketmq_producer "); + return; + } + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + String endpoints = args[0]; + ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder() + .setEndpoints(endpoints) + .build(); + String topic = args[1]; + + // Create the RocketMQ Producer. + final Producer producer = provider.newProducerBuilder() + .setClientConfiguration(clientConfiguration) + .setTopics(topic) + .build(); + final Gson gson = new Gson(); + Map payload = new HashMap<>(); + payload.put("foo", "bar"); + final CloudEvent event = new CloudEventBuilder() + .withId("client-id") + .withSource(URI.create("http://127.0.0.1/rocketmq-client")) + .withType("com.foobar") + .withTime(Time.parseTime("2022-11-09T21:47:12.032198+00:00")) + .withData(gson.toJson(payload).getBytes(StandardCharsets.UTF_8)) + .build(); + // Transform event into message. + final Message message = RocketMqMessageFactory.createWriter(topic).writeBinary(event); + try { + // Send the message. + final SendReceipt sendReceipt = producer.send(message); + System.out.println("Send message successfully, messageId=" + sendReceipt.getMessageId()); + } catch (Exception e) { + System.out.println("Failed to send message"); + e.printStackTrace(); + } + // Close the producer when you don't need it anymore. + producer.close(); + } +} diff --git a/pom.xml b/pom.xml index c927e409a..3e2ec85f3 100644 --- a/pom.xml +++ b/pom.xml @@ -81,6 +81,7 @@ spring sql bom + rocketmq diff --git a/rocketmq/pom.xml b/rocketmq/pom.xml new file mode 100644 index 000000000..ff26dfb0a --- /dev/null +++ b/rocketmq/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + + cloudevents-parent + io.cloudevents + 2.5.0-SNAPSHOT + + + cloudevents-rocketmq + CloudEvents - RocketMQ Binding + jar + + + 5.0.4 + io.cloudevents.rocketmq + + + + + io.cloudevents + cloudevents-core + ${project.version} + + + + org.apache.rocketmq + rocketmq-client-java + ${rocketmq.version} + + + + + org.junit.jupiter + junit-jupiter + ${junit-jupiter.version} + test + + + io.cloudevents + cloudevents-core + tests + test-jar + ${project.version} + test + + + org.assertj + assertj-core + ${assertj-core.version} + test + + + diff --git a/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketMqMessageFactory.java b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketMqMessageFactory.java new file mode 100644 index 000000000..5eb454baf --- /dev/null +++ b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketMqMessageFactory.java @@ -0,0 +1,80 @@ +/* + * 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.rocketmq; + +import io.cloudevents.core.message.MessageReader; +import io.cloudevents.core.message.MessageWriter; +import io.cloudevents.core.message.impl.GenericStructuredMessageReader; +import io.cloudevents.core.message.impl.MessageUtils; +import io.cloudevents.rw.CloudEventWriter; +import java.nio.ByteBuffer; +import java.util.Map; +import org.apache.rocketmq.client.apis.message.Message; +import org.apache.rocketmq.client.apis.message.MessageView; + +/** + * A factory class providing convenience methods for creating {@link MessageReader} and {@link MessageWriter} instances + * based on RocketMQ {@link MessageView} and {@link Message}. + */ +public class RocketMqMessageFactory { + private RocketMqMessageFactory() { + // prevent instantiation + } + + /** + * Creates a {@link MessageReader} to read a RocketMQ {@link MessageView}. + * + * @param message The RocketMQ {@link MessageView} to read from. + * @return A {@link MessageReader} that can read the given {@link MessageView} to a {@link io.cloudevents.CloudEvent} representation. + */ + public static MessageReader createReader(final MessageView message) { + final ByteBuffer byteBuffer = message.getBody(); + byte[] body = new byte[byteBuffer.remaining()]; + byteBuffer.get(body); + final Map properties = message.getProperties(); + final String contentType = properties.get(RocketmqConstants.PROPERTY_CONTENT_TYPE); + return createReader(contentType, properties, body); + } + + /** + * Creates a {@link MessageReader} using the content type, properties, and body of a RocketMQ {@link MessageView}. + * + * @param contentType The content type of the message payload. + * @param properties The properties of the RocketMQ message containing CloudEvent metadata (attributes and/or extensions). + * @param body The message body as byte array. + * @return A {@link MessageReader} capable of parsing a {@link io.cloudevents.CloudEvent} from the content-type, properties, and payload of a RocketMQ message. + */ + public static MessageReader createReader(final String contentType, final Map properties, final byte[] body) { + return MessageUtils.parseStructuredOrBinaryMessage( + () -> contentType, + format -> new GenericStructuredMessageReader(format, body), + () -> properties.get(RocketmqConstants.MESSAGE_PROPERTY_SPEC_VERSION), + sv -> new RocketmqBinaryMessageReader(sv, properties, contentType, body) + ); + } + + /** + * Creates a {@link MessageWriter} instance capable of translating a {@link io.cloudevents.CloudEvent} to a RocketMQ {@link Message}. + * + * @param topic The topic to which the created RocketMQ message will be sent. + * @return A {@link MessageWriter} capable of converting a {@link io.cloudevents.CloudEvent} to a RocketMQ {@link Message}. + */ + public static MessageWriter, Message> createWriter(final String topic) { + return new RocketmqMessageWriter(topic); + } +} diff --git a/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqBinaryMessageReader.java b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqBinaryMessageReader.java new file mode 100644 index 000000000..5439f44ab --- /dev/null +++ b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqBinaryMessageReader.java @@ -0,0 +1,107 @@ +/* + * 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.rocketmq; + +import io.cloudevents.SpecVersion; +import io.cloudevents.core.data.BytesCloudEventData; +import io.cloudevents.core.message.impl.BaseGenericBinaryMessageReaderImpl; +import java.util.Arrays; +import java.util.Map; +import java.util.function.BiConsumer; + +/** + * A RocketMQ message reader that can be read as a CloudEvent. + */ +final class RocketmqBinaryMessageReader extends BaseGenericBinaryMessageReaderImpl { + private final String contentType; + private final Map messageProperties; + + /** + * Create an instance of an RocketMQ message reader. + * + * @param specVersion The version of the cloud event message. + * @param messageProperties The properties of the RocketMQ message that contains. + * @param contentType The content-type property of the RocketMQ message or {@code null} if the message content type if unknown. + * @param body The message payload or {@code null}/{@link RocketmqConstants#EMPTY_BODY} if the message does not contain any payload. + */ + RocketmqBinaryMessageReader(final SpecVersion specVersion, Map messageProperties, + final String contentType, final byte[] body) { + super(specVersion, body != null && !Arrays.equals(RocketmqConstants.EMPTY_BODY, body) && body.length > 0 ? BytesCloudEventData.wrap(body) : null); + this.contentType = contentType; + this.messageProperties = messageProperties; + } + + @Override + protected boolean isContentTypeHeader(String key) { + return key.equals(RocketmqConstants.PROPERTY_CONTENT_TYPE); + } + + /** + * Tests whether the given property key belongs to cloud events headers. + * + * @param key The key to test for. + * @return True if the specified key belongs to cloud events headers. + */ + @Override + protected boolean isCloudEventsHeader(String key) { + final int prefixLength = RocketmqConstants.CE_PREFIX.length(); + return key.length() > prefixLength && key.startsWith(RocketmqConstants.CE_PREFIX); + } + + /** + * Transforms a RocketMQ message property key into a CloudEvents attribute or extension key. + *

+ * This method removes the {@link RocketmqConstants#CE_PREFIX} prefix from the given key, + * assuming that the key has already been determined to be a CloudEvents header by + * {@link #isCloudEventsHeader(String)}. + * + * @param key The RocketMQ message property key with the CloudEvents header prefix. + * @return The CloudEvents attribute or extension key without the prefix. + */ + @Override + protected String toCloudEventsKey(String key) { + return key.substring(RocketmqConstants.CE_PREFIX.length()); + } + + @Override + protected void forEachHeader(BiConsumer fn) { + if (contentType != null) { + // visit the content-type message property + fn.accept(RocketmqConstants.PROPERTY_CONTENT_TYPE, contentType); + } + // visit message properties + messageProperties.forEach((k, v) -> { + if (k != null && v != null) { + fn.accept(k, v); + } + }); + } + + /** + * Gets the cloud event representation of the value. + *

+ * This method simply returns the string representation of the type of value passed as argument. + * + * @param value The value of a CloudEvent attribute or extension. + * @return The string representation of the specified value. + */ + @Override + protected String toCloudEventsValue(Object value) { + return value.toString(); + } +} diff --git a/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqConstants.java b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqConstants.java new file mode 100644 index 000000000..f10098521 --- /dev/null +++ b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqConstants.java @@ -0,0 +1,42 @@ +/* + * 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.rocketmq; + +import io.cloudevents.core.message.impl.MessageUtils; +import java.util.Map; + +/** + * Constants and methods used throughout the RocketMQ binding for cloud events. + */ +final class RocketmqConstants { + private RocketmqConstants() { + // prevent instantiation + } + + static final byte[] EMPTY_BODY = new byte[] {(byte) '\0'}; + + /** + * The prefix name for CloudEvent attributes for use in properties of a RocketMQ message. + */ + static final String CE_PREFIX = "CE_"; + + static final Map ATTRIBUTES_TO_PROPERTY_NAMES = MessageUtils.generateAttributesToHeadersMapping(CEA -> CE_PREFIX + CEA); + + static final String PROPERTY_CONTENT_TYPE = "CE_contenttype"; + static final String MESSAGE_PROPERTY_SPEC_VERSION = ATTRIBUTES_TO_PROPERTY_NAMES.get("specversion"); +} diff --git a/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqMessageWriter.java b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqMessageWriter.java new file mode 100644 index 000000000..8f6561f90 --- /dev/null +++ b/rocketmq/src/main/java/io/cloudevents/rocketmq/RocketmqMessageWriter.java @@ -0,0 +1,98 @@ +/* + * 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.rocketmq; + +import io.cloudevents.CloudEventData; +import io.cloudevents.SpecVersion; +import io.cloudevents.core.format.EventFormat; +import io.cloudevents.core.message.MessageWriter; +import io.cloudevents.core.v1.CloudEventV1; +import io.cloudevents.rw.CloudEventContextWriter; +import io.cloudevents.rw.CloudEventRWException; +import io.cloudevents.rw.CloudEventWriter; +import java.util.HashMap; +import java.util.Map; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.message.Message; +import org.apache.rocketmq.client.apis.message.MessageBuilder; + +/** + * The RocketmqMessageWriter class is a CloudEvents message writer for RocketMQ. + * It allows CloudEvents attributes, context attributes, and the event payload to be populated + * in a RocketMQ {@link Message} instance. This class implements the + * {@link MessageWriter} interface for creating and completing CloudEvents messages in a + * RocketMQ-compatible format. + */ +final class RocketmqMessageWriter implements MessageWriter, Message>, CloudEventWriter { + private final Map messageProperties; + private final MessageBuilder messageBuilder; + + /** + * Create a RocketMQ message writer. + * + * @param topic message's topic. + */ + RocketmqMessageWriter(String topic) { + this.messageProperties = new HashMap<>(); + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + this.messageBuilder = provider.newMessageBuilder(); + messageBuilder.setTopic(topic); + } + + @Override + public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException { + if (name.equals(CloudEventV1.DATACONTENTTYPE)) { + messageProperties.put(RocketmqConstants.PROPERTY_CONTENT_TYPE, value); + return this; + } + String propertyName = RocketmqConstants.ATTRIBUTES_TO_PROPERTY_NAMES.get(name); + if (propertyName == null) { + propertyName = name; + } + messageProperties.put(propertyName, value); + return this; + } + + @Override + public CloudEventWriter create(SpecVersion version) throws CloudEventRWException { + messageProperties.put(RocketmqConstants.MESSAGE_PROPERTY_SPEC_VERSION, version.toString()); + return this; + } + + @Override + public Message setEvent(EventFormat format, byte[] value) throws CloudEventRWException { + messageProperties.put(RocketmqConstants.PROPERTY_CONTENT_TYPE, format.serializedContentType()); + messageBuilder.setBody(value); + messageProperties.forEach(messageBuilder::addProperty); + return messageBuilder.build(); + } + + @Override + public Message end(CloudEventData data) throws CloudEventRWException { + messageBuilder.setBody(data.toBytes()); + messageProperties.forEach(messageBuilder::addProperty); + return messageBuilder.build(); + } + + @Override + public Message end() throws CloudEventRWException { + messageBuilder.setBody(RocketmqConstants.EMPTY_BODY); + messageProperties.forEach(messageBuilder::addProperty); + return messageBuilder.build(); + } +} diff --git a/rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageFactoryTest.java b/rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageFactoryTest.java new file mode 100644 index 000000000..ea11e2a8f --- /dev/null +++ b/rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageFactoryTest.java @@ -0,0 +1,233 @@ +/* + * 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.rocketmq; + +import io.cloudevents.CloudEvent; +import io.cloudevents.SpecVersion; +import io.cloudevents.core.message.Encoding; +import io.cloudevents.core.message.MessageReader; +import io.cloudevents.core.mock.CSVFormat; +import io.cloudevents.core.test.Data; +import io.cloudevents.core.v03.CloudEventV03; +import io.cloudevents.core.v1.CloudEventV1; +import io.cloudevents.types.Time; +import java.util.AbstractMap; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests verifying the behavior of the {@code RocketmqMessageFactory}. + */ +public class RocketmqMessageFactoryTest { + private static final String PREFIX_TEMPLATE = RocketmqConstants.CE_PREFIX + "%s"; + + private static final String DATA_CONTENT_TYPE_NULL = null; + private static final byte[] DATA_PAYLOAD_NULL = null; + + @ParameterizedTest() + @MethodSource("binaryTestArguments") + public void readBinary(final Map props, final String contentType, final byte[] body, final CloudEvent event) { + final MessageReader reader = RocketMqMessageFactory.createReader(contentType, props, body); + assertThat(reader.getEncoding()).isEqualTo(Encoding.BINARY); + assertThat(reader.toEvent()).isEqualTo(event); + } + + @ParameterizedTest() + @MethodSource("io.cloudevents.core.test.Data#allEventsWithoutExtensions") + public void readStructured(final CloudEvent event) { + final String contentType = CSVFormat.INSTANCE.serializedContentType() + "; charset=utf8"; + final byte[] contentPayload = CSVFormat.INSTANCE.serialize(event); + + final MessageReader reader = RocketMqMessageFactory.createReader(contentType, null, contentPayload); + assertThat(reader.getEncoding()).isEqualTo(Encoding.STRUCTURED); + assertThat(reader.toEvent()).isEqualTo(event); + } + + private static Stream binaryTestArguments() { + + return Stream.of( + // V03 + Arguments.of( + properties( + property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()), + property(CloudEventV03.ID, Data.ID), + property(CloudEventV03.TYPE, Data.TYPE), + property(CloudEventV03.SOURCE, Data.SOURCE.toString()), + property("ignored", "ignore") + ), + DATA_CONTENT_TYPE_NULL, + DATA_PAYLOAD_NULL, + Data.V03_MIN + ), + Arguments.of( + properties( + property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()), + property(CloudEventV03.ID, Data.ID), + property(CloudEventV03.TYPE, Data.TYPE), + property(CloudEventV03.SOURCE, Data.SOURCE.toString()), + property(CloudEventV03.SCHEMAURL, Data.DATASCHEMA.toString()), + property(CloudEventV03.SUBJECT, Data.SUBJECT), + property(CloudEventV03.TIME, Time.writeTime(Data.TIME)), + property("ignored", "ignore") + ), + Data.DATACONTENTTYPE_JSON, + Data.DATA_JSON_SERIALIZED, + Data.V03_WITH_JSON_DATA + ), + Arguments.of( + properties( + property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()), + property(CloudEventV03.ID, Data.ID), + property(CloudEventV03.TYPE, Data.TYPE), + property(CloudEventV03.SOURCE, Data.SOURCE.toString()), + property(CloudEventV03.SCHEMAURL, Data.DATASCHEMA.toString()), + property(CloudEventV03.SUBJECT, Data.SUBJECT), + property(CloudEventV03.TIME, Time.writeTime(Data.TIME)), + property("astring", "aaa"), + property("aboolean", "true"), + property("anumber", "10"), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_JSON, + Data.DATA_JSON_SERIALIZED, + Data.V03_WITH_JSON_DATA_WITH_EXT_STRING + ), + Arguments.of( + properties( + property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()), + property(CloudEventV03.ID, Data.ID), + property(CloudEventV03.TYPE, Data.TYPE), + property(CloudEventV03.SOURCE, Data.SOURCE.toString()), + property(CloudEventV03.SUBJECT, Data.SUBJECT), + property(CloudEventV03.TIME, Time.writeTime(Data.TIME)), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_XML, + Data.DATA_XML_SERIALIZED, + Data.V03_WITH_XML_DATA + ), + Arguments.of( + properties( + property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()), + property(CloudEventV03.ID, Data.ID), + property(CloudEventV03.TYPE, Data.TYPE), + property(CloudEventV03.SOURCE, Data.SOURCE.toString()), + property(CloudEventV03.SUBJECT, Data.SUBJECT), + property(CloudEventV03.TIME, Time.writeTime(Data.TIME)), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_TEXT, + Data.DATA_TEXT_SERIALIZED, + Data.V03_WITH_TEXT_DATA + ), + // V1 + Arguments.of( + properties( + property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()), + property(CloudEventV1.ID, Data.ID), + property(CloudEventV1.TYPE, Data.TYPE), + property(CloudEventV1.SOURCE, Data.SOURCE.toString()), + property("ignored", "ignored") + ), + DATA_CONTENT_TYPE_NULL, + DATA_PAYLOAD_NULL, + Data.V1_MIN + ), + Arguments.of( + properties( + property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()), + property(CloudEventV1.ID, Data.ID), + property(CloudEventV1.TYPE, Data.TYPE), + property(CloudEventV1.SOURCE, Data.SOURCE.toString()), + property(CloudEventV1.DATASCHEMA, Data.DATASCHEMA.toString()), + property(CloudEventV1.SUBJECT, Data.SUBJECT), + property(CloudEventV1.TIME, Time.writeTime(Data.TIME)), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_JSON, + Data.DATA_JSON_SERIALIZED, + Data.V1_WITH_JSON_DATA + ), + Arguments.of( + properties( + property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()), + property(CloudEventV1.ID, Data.ID), + property(CloudEventV1.TYPE, Data.TYPE), + property(CloudEventV1.SOURCE, Data.SOURCE.toString()), + property(CloudEventV1.DATASCHEMA, Data.DATASCHEMA.toString()), + property(CloudEventV1.SUBJECT, Data.SUBJECT), + property(CloudEventV1.TIME, Time.writeTime(Data.TIME)), + property("astring", "aaa"), + property("aboolean", "true"), + property("anumber", "10"), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_JSON, + Data.DATA_JSON_SERIALIZED, + Data.V1_WITH_JSON_DATA_WITH_EXT_STRING + ), + Arguments.of( + properties( + property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()), + property(CloudEventV1.ID, Data.ID), + property(CloudEventV1.TYPE, Data.TYPE), + property(CloudEventV1.SOURCE, Data.SOURCE.toString()), + property(CloudEventV1.SUBJECT, Data.SUBJECT), + property(CloudEventV1.TIME, Time.writeTime(Data.TIME)), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_XML, + Data.DATA_XML_SERIALIZED, + Data.V1_WITH_XML_DATA + ), + Arguments.of( + properties( + property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()), + property(CloudEventV1.ID, Data.ID), + property(CloudEventV1.TYPE, Data.TYPE), + property(CloudEventV1.SOURCE, Data.SOURCE.toString()), + property(CloudEventV1.SUBJECT, Data.SUBJECT), + property(CloudEventV1.TIME, Time.writeTime(Data.TIME)), + property("ignored", "ignored") + ), + Data.DATACONTENTTYPE_TEXT, + Data.DATA_TEXT_SERIALIZED, + Data.V1_WITH_TEXT_DATA + ) + ); + } + + private static AbstractMap.SimpleEntry property(final String name, final String value) { + return name.equalsIgnoreCase("ignored") ? + new AbstractMap.SimpleEntry<>(name, value) : + new AbstractMap.SimpleEntry<>(String.format(PREFIX_TEMPLATE, name), value); + } + + @SafeVarargs + private static Map properties(final AbstractMap.SimpleEntry... entries) { + return Stream.of(entries) + .collect(Collectors.toMap(AbstractMap.SimpleEntry::getKey, AbstractMap.SimpleEntry::getValue)); + + } +} diff --git a/rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageWriterTest.java b/rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageWriterTest.java new file mode 100644 index 000000000..c6d1b2c4e --- /dev/null +++ b/rocketmq/src/test/java/io/cloudevents/rocketmq/RocketmqMessageWriterTest.java @@ -0,0 +1,117 @@ +/* + * 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.rocketmq; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.format.EventFormat; +import io.cloudevents.core.message.MessageWriter; +import io.cloudevents.core.mock.CSVFormat; +import io.cloudevents.core.v1.CloudEventV1; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import org.apache.rocketmq.client.apis.ClientServiceProvider; +import org.apache.rocketmq.client.apis.message.Message; +import org.apache.rocketmq.client.apis.message.MessageBuilder; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RocketmqMessageWriterTest { + + /** + * Verifies that a binary CloudEvent message can be successfully represented + * as a RocketMQ message. + */ + @ParameterizedTest() + @MethodSource("io.cloudevents.core.test.Data#allEventsWithStringExtensions") + public void testWriteBinaryCloudEventToRocketmqRepresentation(final CloudEvent binaryEvent) { + + String topic = "foobar"; + final Message expectedMessage = translateBinaryEvent(topic, binaryEvent); + + final MessageWriter writer = RocketMqMessageFactory.createWriter(topic); + final Message actualMessage = writer.writeBinary(binaryEvent); + + assertThat(Objects.toString(actualMessage.getBody())).isEqualTo(Objects.toString(expectedMessage.getBody())); + assertThat(actualMessage.getProperties()).isEqualTo(expectedMessage.getProperties()); + } + + /** + * Verifies that a structured CloudEvent message (in CSV format) can be successfully represented + * as a RocketMQ message. + */ + @ParameterizedTest() + @MethodSource("io.cloudevents.core.test.Data#allEventsWithoutExtensions") + public void testWriteStructuredCloudEventToRocketmqRepresentation(final CloudEvent event) { + final EventFormat format = CSVFormat.INSTANCE; + final Message expectedMessage = translateStructured(event, format); + + String topic = "foobar"; + final MessageWriter writer = RocketMqMessageFactory.createWriter(topic); + final Message actualMessage = writer.writeStructured(event, format.serializedContentType()); + + assertThat(Objects.toString(actualMessage.getBody())).isEqualTo(Objects.toString(expectedMessage.getBody())); + assertThat(actualMessage.getProperties()).isEqualTo(expectedMessage.getProperties()); + } + + private Message translateBinaryEvent(final String topic, final CloudEvent event) { + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + + final MessageBuilder messageBuilder = provider.newMessageBuilder(); + messageBuilder.setTopic(topic); + messageBuilder.setBody(RocketmqConstants.EMPTY_BODY); + + final Map map = new HashMap<>(); + if (!event.getAttributeNames().isEmpty()) { + event.getAttributeNames().forEach(name -> { + if (name.equals(CloudEventV1.DATACONTENTTYPE) && event.getAttribute(name) != null) { + map.put(RocketmqConstants.PROPERTY_CONTENT_TYPE, event.getAttribute(name).toString()); + } else { + addProperty(map, name, Objects.toString(event.getAttribute(name)), true); + } + }); + } + if (!event.getExtensionNames().isEmpty()) { + event.getExtensionNames().forEach(name -> addProperty(map, name, Objects.toString(event.getExtension(name)), false)); + } + map.forEach(messageBuilder::addProperty); + if (event.getData() != null) { + messageBuilder.setBody(event.getData().toBytes()); + } + return messageBuilder.build(); + } + + private Message translateStructured(final CloudEvent event, final EventFormat format) { + final ClientServiceProvider provider = ClientServiceProvider.loadService(); + final MessageBuilder messageBuilder = provider.newMessageBuilder(); + messageBuilder.setTopic("foobar"); + messageBuilder.addProperty(RocketmqConstants.PROPERTY_CONTENT_TYPE, format.serializedContentType()); + messageBuilder.setBody(format.serialize(event)); + return messageBuilder.build(); + } + + private void addProperty(final Map map, final String name, final String value, final boolean prefix) { + if (prefix) { + map.put(String.format(RocketmqConstants.CE_PREFIX + "%s", name), value); + } else { + map.put(name, value); + } + } +} From 98d6b90df438aba71a98c0c2c440a234113920f0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 10:55:49 +0200 Subject: [PATCH 11/28] Bump to 3.0.0-SNAPSHOT (#571) Signed-off-by: GitHub Co-authored-by: pierDipi Signed-off-by: Alex Collins --- amqp/pom.xml | 2 +- api/pom.xml | 2 +- benchmarks/pom.xml | 2 +- bom/pom.xml | 2 +- core/pom.xml | 2 +- examples/amqp-proton/pom.xml | 2 +- examples/basic-http/pom.xml | 2 +- examples/kafka/pom.xml | 2 +- examples/pom.xml | 2 +- examples/restful-ws-microprofile-liberty/pom.xml | 2 +- examples/restful-ws-quarkus/pom.xml | 2 +- examples/restful-ws-spring-boot/pom.xml | 2 +- examples/rocketmq/pom.xml | 2 +- examples/spring-function/pom.xml | 2 +- examples/spring-reactive/pom.xml | 2 +- examples/spring-rsocket/pom.xml | 2 +- examples/vertx/pom.xml | 2 +- formats/json-jackson/pom.xml | 2 +- formats/protobuf/pom.xml | 2 +- formats/xml/pom.xml | 2 +- http/basic/pom.xml | 2 +- http/restful-ws-integration-tests/pom.xml | 2 +- http/restful-ws-integration-tests/restful-ws-common/pom.xml | 2 +- http/restful-ws-integration-tests/restful-ws-jersey/pom.xml | 2 +- http/restful-ws-integration-tests/restful-ws-resteasy/pom.xml | 2 +- http/restful-ws-integration-tests/restful-ws-spring/pom.xml | 2 +- http/restful-ws-jakarta-integration-tests/pom.xml | 2 +- .../restful-ws-jakarta-common/pom.xml | 2 +- .../restful-ws-liberty/pom.xml | 2 +- .../restful-ws-resteasy/pom.xml | 2 +- http/restful-ws-jakarta/pom.xml | 2 +- http/restful-ws/pom.xml | 2 +- http/vertx/pom.xml | 2 +- kafka/pom.xml | 2 +- pom.xml | 2 +- rocketmq/pom.xml | 2 +- spring/pom.xml | 2 +- sql/pom.xml | 2 +- 38 files changed, 38 insertions(+), 38 deletions(-) diff --git a/amqp/pom.xml b/amqp/pom.xml index 23853c594..865acfdc5 100644 --- a/amqp/pom.xml +++ b/amqp/pom.xml @@ -6,7 +6,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-amqp-proton diff --git a/api/pom.xml b/api/pom.xml index cb657fa82..adb849480 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,7 +24,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-api diff --git a/benchmarks/pom.xml b/benchmarks/pom.xml index 78e6b87cd..2f150fbf7 100644 --- a/benchmarks/pom.xml +++ b/benchmarks/pom.xml @@ -21,7 +21,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-benchmarks diff --git a/bom/pom.xml b/bom/pom.xml index 8441369ce..8343f7644 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -22,7 +22,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-bom diff --git a/core/pom.xml b/core/pom.xml index 5104d4ff8..2e777d559 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -22,7 +22,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-core diff --git a/examples/amqp-proton/pom.xml b/examples/amqp-proton/pom.xml index 6919bf4f2..4ef09c5ce 100644 --- a/examples/amqp-proton/pom.xml +++ b/examples/amqp-proton/pom.xml @@ -3,7 +3,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/basic-http/pom.xml b/examples/basic-http/pom.xml index 7b57dbe25..153c0de78 100644 --- a/examples/basic-http/pom.xml +++ b/examples/basic-http/pom.xml @@ -21,7 +21,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/kafka/pom.xml b/examples/kafka/pom.xml index e39e9d8eb..018384e61 100644 --- a/examples/kafka/pom.xml +++ b/examples/kafka/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 7a8bd1538..de74f57fd 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ cloudevents-parent io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/restful-ws-microprofile-liberty/pom.xml b/examples/restful-ws-microprofile-liberty/pom.xml index 0cfbd64fc..86680c0b9 100644 --- a/examples/restful-ws-microprofile-liberty/pom.xml +++ b/examples/restful-ws-microprofile-liberty/pom.xml @@ -3,7 +3,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/examples/restful-ws-quarkus/pom.xml b/examples/restful-ws-quarkus/pom.xml index c6a145837..68b11715d 100644 --- a/examples/restful-ws-quarkus/pom.xml +++ b/examples/restful-ws-quarkus/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 cloudevents-restful-ws-quarkus-example diff --git a/examples/restful-ws-spring-boot/pom.xml b/examples/restful-ws-spring-boot/pom.xml index 5a66b8f89..2475adc0f 100644 --- a/examples/restful-ws-spring-boot/pom.xml +++ b/examples/restful-ws-spring-boot/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/rocketmq/pom.xml b/examples/rocketmq/pom.xml index c40f24a6a..022ac49cc 100644 --- a/examples/rocketmq/pom.xml +++ b/examples/rocketmq/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/spring-function/pom.xml b/examples/spring-function/pom.xml index 42a071d1f..5a0047f1a 100644 --- a/examples/spring-function/pom.xml +++ b/examples/spring-function/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/spring-reactive/pom.xml b/examples/spring-reactive/pom.xml index 86f2bdac7..2a4489f04 100644 --- a/examples/spring-reactive/pom.xml +++ b/examples/spring-reactive/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/spring-rsocket/pom.xml b/examples/spring-rsocket/pom.xml index 505339f0e..151a7fe84 100644 --- a/examples/spring-rsocket/pom.xml +++ b/examples/spring-rsocket/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/examples/vertx/pom.xml b/examples/vertx/pom.xml index 76601c4c0..7fccab33e 100644 --- a/examples/vertx/pom.xml +++ b/examples/vertx/pom.xml @@ -5,7 +5,7 @@ cloudevents-examples io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 diff --git a/formats/json-jackson/pom.xml b/formats/json-jackson/pom.xml index 1994883af..b39374299 100644 --- a/formats/json-jackson/pom.xml +++ b/formats/json-jackson/pom.xml @@ -22,7 +22,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/formats/protobuf/pom.xml b/formats/protobuf/pom.xml index e76c935f1..0e4dc3da9 100644 --- a/formats/protobuf/pom.xml +++ b/formats/protobuf/pom.xml @@ -23,7 +23,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/formats/xml/pom.xml b/formats/xml/pom.xml index 9f57fbf23..9c0bf6319 100644 --- a/formats/xml/pom.xml +++ b/formats/xml/pom.xml @@ -23,7 +23,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/http/basic/pom.xml b/http/basic/pom.xml index 446744f86..b473c81b0 100644 --- a/http/basic/pom.xml +++ b/http/basic/pom.xml @@ -21,7 +21,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/http/restful-ws-integration-tests/pom.xml b/http/restful-ws-integration-tests/pom.xml index e35b4b383..fa0b5b307 100644 --- a/http/restful-ws-integration-tests/pom.xml +++ b/http/restful-ws-integration-tests/pom.xml @@ -22,7 +22,7 @@ cloudevents-parent io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/http/restful-ws-integration-tests/restful-ws-common/pom.xml b/http/restful-ws-integration-tests/restful-ws-common/pom.xml index 1f2b7bb55..ad95ba0e5 100644 --- a/http/restful-ws-integration-tests/restful-ws-common/pom.xml +++ b/http/restful-ws-integration-tests/restful-ws-common/pom.xml @@ -22,7 +22,7 @@ cloudevents-http-restful-ws-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-integration-tests/restful-ws-jersey/pom.xml b/http/restful-ws-integration-tests/restful-ws-jersey/pom.xml index 601afea1b..d1383d4e9 100644 --- a/http/restful-ws-integration-tests/restful-ws-jersey/pom.xml +++ b/http/restful-ws-integration-tests/restful-ws-jersey/pom.xml @@ -22,7 +22,7 @@ cloudevents-http-restful-ws-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-integration-tests/restful-ws-resteasy/pom.xml b/http/restful-ws-integration-tests/restful-ws-resteasy/pom.xml index d1f3c5a73..0ed365e9b 100644 --- a/http/restful-ws-integration-tests/restful-ws-resteasy/pom.xml +++ b/http/restful-ws-integration-tests/restful-ws-resteasy/pom.xml @@ -22,7 +22,7 @@ cloudevents-http-restful-ws-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-integration-tests/restful-ws-spring/pom.xml b/http/restful-ws-integration-tests/restful-ws-spring/pom.xml index 54a4e7e4e..a4d0c15f3 100644 --- a/http/restful-ws-integration-tests/restful-ws-spring/pom.xml +++ b/http/restful-ws-integration-tests/restful-ws-spring/pom.xml @@ -22,7 +22,7 @@ cloudevents-http-restful-ws-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-jakarta-integration-tests/pom.xml b/http/restful-ws-jakarta-integration-tests/pom.xml index 11e49266c..683c25981 100644 --- a/http/restful-ws-jakarta-integration-tests/pom.xml +++ b/http/restful-ws-jakarta-integration-tests/pom.xml @@ -5,7 +5,7 @@ cloudevents-parent io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml 4.0.0 diff --git a/http/restful-ws-jakarta-integration-tests/restful-ws-jakarta-common/pom.xml b/http/restful-ws-jakarta-integration-tests/restful-ws-jakarta-common/pom.xml index d9d63d571..1a010d5b2 100644 --- a/http/restful-ws-jakarta-integration-tests/restful-ws-jakarta-common/pom.xml +++ b/http/restful-ws-jakarta-integration-tests/restful-ws-jakarta-common/pom.xml @@ -3,7 +3,7 @@ cloudevents-http-restful-ws-jakarta-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-jakarta-integration-tests/restful-ws-liberty/pom.xml b/http/restful-ws-jakarta-integration-tests/restful-ws-liberty/pom.xml index 1789438fb..c3aca0774 100644 --- a/http/restful-ws-jakarta-integration-tests/restful-ws-liberty/pom.xml +++ b/http/restful-ws-jakarta-integration-tests/restful-ws-liberty/pom.xml @@ -5,7 +5,7 @@ cloudevents-http-restful-ws-jakarta-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-jakarta-integration-tests/restful-ws-resteasy/pom.xml b/http/restful-ws-jakarta-integration-tests/restful-ws-resteasy/pom.xml index 08a87fa73..95fa033e9 100644 --- a/http/restful-ws-jakarta-integration-tests/restful-ws-resteasy/pom.xml +++ b/http/restful-ws-jakarta-integration-tests/restful-ws-resteasy/pom.xml @@ -3,7 +3,7 @@ cloudevents-http-restful-ws-jakarta-integration-tests io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../ 4.0.0 diff --git a/http/restful-ws-jakarta/pom.xml b/http/restful-ws-jakarta/pom.xml index 6d64b836c..5f6367f3d 100644 --- a/http/restful-ws-jakarta/pom.xml +++ b/http/restful-ws-jakarta/pom.xml @@ -21,7 +21,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/http/restful-ws/pom.xml b/http/restful-ws/pom.xml index fd6550a6f..d9ab6ad73 100644 --- a/http/restful-ws/pom.xml +++ b/http/restful-ws/pom.xml @@ -21,7 +21,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/http/vertx/pom.xml b/http/vertx/pom.xml index 7ddcec1c7..123a41d37 100644 --- a/http/vertx/pom.xml +++ b/http/vertx/pom.xml @@ -22,7 +22,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml diff --git a/kafka/pom.xml b/kafka/pom.xml index e4309f781..787062c40 100644 --- a/kafka/pom.xml +++ b/kafka/pom.xml @@ -23,7 +23,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-kafka diff --git a/pom.xml b/pom.xml index 3e2ec85f3..b70da0c49 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT pom CloudEvents diff --git a/rocketmq/pom.xml b/rocketmq/pom.xml index ff26dfb0a..e7d2bad2c 100644 --- a/rocketmq/pom.xml +++ b/rocketmq/pom.xml @@ -23,7 +23,7 @@ cloudevents-parent io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-rocketmq diff --git a/spring/pom.xml b/spring/pom.xml index 84c78f0dc..038fecb8d 100644 --- a/spring/pom.xml +++ b/spring/pom.xml @@ -23,7 +23,7 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT cloudevents-spring diff --git a/sql/pom.xml b/sql/pom.xml index 63d4f5551..9c2f5f7fb 100644 --- a/sql/pom.xml +++ b/sql/pom.xml @@ -5,7 +5,7 @@ cloudevents-parent io.cloudevents - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT 4.0.0 From 5c44a32dd2a43e9966f1d33689606b97d31786f3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 12:08:54 +0200 Subject: [PATCH 12/28] Bump maven-gpg-plugin from 1.6 to 3.1.0 (#564) Bumps [maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 1.6 to 3.1.0. - [Commits](https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-1.6...maven-gpg-plugin-3.1.0) --- updated-dependencies: - dependency-name: org.apache.maven.plugins:maven-gpg-plugin dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Signed-off-by: Alex Collins --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index b70da0c49..457c9d95a 100644 --- a/pom.xml +++ b/pom.xml @@ -244,7 +244,7 @@ org.apache.maven.plugins maven-gpg-plugin - 1.6 + 3.1.0 sign-artifacts From eceb42ae5353779a32cd3c33bc62dd6fb7817337 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:02:26 -0700 Subject: [PATCH 13/28] fix: avroturbo Signed-off-by: Alex Collins --- formats/{avro => avro-turbo}/README.md | 0 formats/{avro => avro-turbo}/pom.xml | 12 +- .../src/main/avro/cloudevents.avsc | 0 .../avroturbo/AvroTurboFormat.java | 127 ++++++++++++++++++ .../io.cloudevents.core.format.EventFormat | 1 + .../cloudevents/avro/AvroTurboFormatTest.java | 76 +++++++++++ .../java/io/cloudevents/avro/AvroFormat.java | 127 ------------------ .../io.cloudevents.core.format.EventFormat | 1 - .../io/cloudevents/avro/AvroFormatTest.java | 78 ----------- pom.xml | 2 +- 10 files changed, 214 insertions(+), 210 deletions(-) rename formats/{avro => avro-turbo}/README.md (100%) rename formats/{avro => avro-turbo}/pom.xml (91%) rename formats/{avro => avro-turbo}/src/main/avro/cloudevents.avsc (100%) create mode 100644 formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java create mode 100644 formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat create mode 100644 formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java delete mode 100644 formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java delete mode 100644 formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat delete mode 100644 formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java diff --git a/formats/avro/README.md b/formats/avro-turbo/README.md similarity index 100% rename from formats/avro/README.md rename to formats/avro-turbo/README.md diff --git a/formats/avro/pom.xml b/formats/avro-turbo/pom.xml similarity index 91% rename from formats/avro/pom.xml rename to formats/avro-turbo/pom.xml index 7db6ef1df..8d7560c1b 100644 --- a/formats/avro/pom.xml +++ b/formats/avro-turbo/pom.xml @@ -23,12 +23,18 @@ io.cloudevents cloudevents-parent - 2.5.0-SNAPSHOT + 3.0.0-SNAPSHOT ../../pom.xml - cloudevents-avro - CloudEvents - Avro + cloudevents-avro-turbo + CloudEvents - Avro Turbo + + + + 2.13.3 + io.cloudevents.formats.avroturbo + diff --git a/formats/avro/src/main/avro/cloudevents.avsc b/formats/avro-turbo/src/main/avro/cloudevents.avsc similarity index 100% rename from formats/avro/src/main/avro/cloudevents.avsc rename to formats/avro-turbo/src/main/avro/cloudevents.avsc diff --git a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java new file mode 100644 index 000000000..2ae8ef9dd --- /dev/null +++ b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java @@ -0,0 +1,127 @@ +/* + * 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.avroturbo; + + +import io.cloudevents.CloudEvent; +import io.cloudevents.CloudEventData; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.data.BytesCloudEventData; +import io.cloudevents.core.format.EventDeserializationException; +import io.cloudevents.core.format.EventFormat; +import io.cloudevents.core.format.EventSerializationException; +import io.cloudevents.rw.CloudEventDataMapper; +import io.cloudevents.v1.avro.CloudEvent.Builder; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.time.ZoneOffset; +import java.util.HashMap; +import java.util.Map; + +/** + * An implementation of {@link EventFormat} for the Avro format. + * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_CONTENT_TYPE}. + * It only supports data that is bytes. + */ +public class AvroTurboFormat implements EventFormat { + + public static final String AVRO_CONTENT_TYPE = "application/cloudevents+avroturbo"; + + @Override + public byte[] serialize(CloudEvent from) throws EventSerializationException { + try { + Builder to = io.cloudevents.v1.avro.CloudEvent.newBuilder(); + + // extensions + Map attribute = new HashMap<>(); + for (String name : from.getExtensionNames()) { + Object value = from.getExtension(name); + attribute.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); + } + + to.setSource(from.getSource().toString()) + .setType(from.getType()) + .setId(from.getId()) + .setSubject(from.getSubject()) + .setDatacontenttype(from.getDataContentType()) + .setAttribute(attribute); + + if (from.getTime() != null) + to.setTime(from.getTime().toInstant()); + if (from.getDataSchema() != null) + to.setDataschema(from.getDataSchema().toString()); + + CloudEventData data = from.getData(); + if (data != null) + to.setData(ByteBuffer.wrap(data.toBytes())); + return to.build().toByteBuffer().array(); + } catch (Exception e) { + throw new EventSerializationException(e); + } + } + + @Override + public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) throws EventDeserializationException { + try { + io.cloudevents.v1.avro.CloudEvent from = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); + CloudEventBuilder to = CloudEventBuilder.v1() + .withSource(URI.create(from.getSource())) + .withType(from.getType()) + .withId(from.getType()) + .withSubject(from.getSubject()) + .withDataContentType(from.getDatacontenttype()); + + if (from.getTime() != null) + to.withTime(from.getTime().atOffset(ZoneOffset.UTC)); + if (from.getDataschema() != null) + to.withDataSchema(URI.create(from.getDataschema())); + + // extensions + for (Map.Entry entry : from.getAttribute().entrySet()) { + String name = entry.getKey(); + Object value = entry.getValue(); + // Avro supports boolean, int, string, bytes + if (value instanceof Boolean) + to.withExtension(name, (boolean) value); + else if (value instanceof Integer) + to.withExtension(name, (int) value); + else if (value instanceof String) + to.withExtension(name, (String) value); + else if (value instanceof ByteBuffer) + to.withExtension(name, ((ByteBuffer) value).array()); + else + // this cannot happen, if ever seen, must be bug in this library + throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass())); + } + + if (from.getData() == null) + return to.end(); + else { + CloudEventData data = BytesCloudEventData.wrap(from.getData().array()); + return to.end(mapper.map(data)); + } + } catch (Exception e) { + throw new EventDeserializationException(e); + } + } + + @Override + public String serializedContentType() { + return AVRO_CONTENT_TYPE; + } +} diff --git a/formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat b/formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat new file mode 100644 index 000000000..4274487db --- /dev/null +++ b/formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat @@ -0,0 +1 @@ +io.cloudevents.avroturbo.AvroTurboFormat diff --git a/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java b/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java new file mode 100644 index 000000000..48d2057e7 --- /dev/null +++ b/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java @@ -0,0 +1,76 @@ +/* + * 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.avro; + +import io.cloudevents.CloudEvent; +import io.cloudevents.avroturbo.AvroTurboFormat; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.core.data.BytesCloudEventData; +import io.cloudevents.core.format.EventFormat; +import io.cloudevents.core.provider.EventFormatProvider; +import org.junit.jupiter.api.Test; + +import java.net.URI; +import java.time.Instant; +import java.time.ZoneOffset; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class AvroTurboFormatTest { + + private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroTurboFormat.AVRO_CONTENT_TYPE); + + // TODO - add test cases for + // - null data + // - non-bytes data + // - extension that is bytes + // - invalid extension type + @Test + void format() { + assertNotNull(format); + assertEquals(Collections.singleton("application/cloudevents+avroturbo"), format.deserializableContentTypes()); + + CloudEvent event = CloudEventBuilder.v1() + // mandatory + .withId("") + .withSource(URI.create("")) + .withType("") + // optional + .withTime(Instant.EPOCH.atOffset(ZoneOffset.UTC)) + .withSubject("") + .withDataSchema(URI.create("")) + // extension + // support boolean, int, string, bytes + .withExtension("boolean", false) + .withExtension("int", 0) + .withExtension("string", "") + // omitting bytes, because it is not supported be CloudEvent.equals + .withData("", BytesCloudEventData.wrap(new byte[0])) + .build(); + + byte[] serialized = format.serialize(event); + + assertNotNull(serialized); + + CloudEvent deserialized = format.deserialize(serialized); + + assertEquals(event, deserialized); + + } +} diff --git a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java b/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java deleted file mode 100644 index f910f4156..000000000 --- a/formats/avro/src/main/java/io/cloudevents/avro/AvroFormat.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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.avro; - - -import io.cloudevents.CloudEvent; -import io.cloudevents.CloudEventData; -import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.core.data.BytesCloudEventData; -import io.cloudevents.core.format.EventDeserializationException; -import io.cloudevents.core.format.EventFormat; -import io.cloudevents.core.format.EventSerializationException; -import io.cloudevents.rw.CloudEventDataMapper; -import io.cloudevents.v1.avro.CloudEvent.Builder; - -import java.net.URI; -import java.nio.ByteBuffer; -import java.time.ZoneOffset; -import java.util.HashMap; -import java.util.Map; - -/** - * An implementation of {@link EventFormat} for the Avro format. - * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_CONTENT_TYPE}. - * It only supports data that is bytes. - */ -public class AvroFormat implements EventFormat { - - public static final String AVRO_CONTENT_TYPE = "application/cloudevents+avro"; - - @Override - public byte[] serialize(CloudEvent from) throws EventSerializationException { - try { - Builder to = io.cloudevents.v1.avro.CloudEvent.newBuilder(); - - // extensions - Map attribute = new HashMap<>(); - for (String name : from.getExtensionNames()) { - Object value = from.getExtension(name); - attribute.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); - } - - to.setSource(from.getSource().toString()) - .setType(from.getType()) - .setId(from.getId()) - .setSubject(from.getSubject()) - .setDatacontenttype(from.getDataContentType()) - .setAttribute(attribute); - - if (from.getTime() != null) - to.setTime(from.getTime().toInstant()); - if (from.getDataSchema() != null) - to.setDataschema(from.getDataSchema().toString()); - - CloudEventData data = from.getData(); - if (data != null) - to.setData(ByteBuffer.wrap(data.toBytes())); - return to.build().toByteBuffer().array(); - } catch (Exception e) { - throw new EventSerializationException(e); - } - } - - @Override - public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) throws EventDeserializationException { - try { - io.cloudevents.v1.avro.CloudEvent from = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); - CloudEventBuilder to = CloudEventBuilder.v1() - .withSource(URI.create(from.getSource())) - .withType(from.getType()) - .withId(from.getType()) - .withSubject(from.getSubject()) - .withDataContentType(from.getDatacontenttype()); - - if (from.getTime() != null) - to.withTime(from.getTime().atOffset(ZoneOffset.UTC)); - if (from.getDataschema() != null) - to.withDataSchema(URI.create(from.getDataschema())); - - // extensions - for (Map.Entry entry : from.getAttribute().entrySet()) { - String name = entry.getKey(); - Object value = entry.getValue(); - // Avro supports boolean, int, string, bytes - if (value instanceof Boolean) - to.withExtension(name, (boolean) value); - else if (value instanceof Integer) - to.withExtension(name, (int) value); - else if (value instanceof String) - to.withExtension(name, (String) value); - else if (value instanceof ByteBuffer) - to.withExtension(name, ((ByteBuffer) value).array()); - else - // this cannot happen, if ever seen, must be bug in this library - throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass())); - } - - if (from.getData() == null) - return to.end(); - else { - CloudEventData data = BytesCloudEventData.wrap(from.getData().array()); - return to.end(mapper.map(data)); - } - } catch (Exception e) { - throw new EventDeserializationException(e); - } - } - - @Override - public String serializedContentType() { - return AVRO_CONTENT_TYPE; - } -} diff --git a/formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat b/formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat deleted file mode 100644 index 31cb85d6d..000000000 --- a/formats/avro/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat +++ /dev/null @@ -1 +0,0 @@ -io.cloudevents.avro.AvroFormat diff --git a/formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java b/formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java deleted file mode 100644 index 5dd5804d5..000000000 --- a/formats/avro/src/test/java/io/cloudevents/avro/AvroFormatTest.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * 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.avro; - -import io.cloudevents.CloudEvent; -import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.core.data.BytesCloudEventData; -import io.cloudevents.core.data.PojoCloudEventData; -import io.cloudevents.core.format.EventFormat; -import io.cloudevents.core.provider.EventFormatProvider; -import org.junit.jupiter.api.Test; - -import java.net.URI; -import java.time.Instant; -import java.time.OffsetDateTime; -import java.time.ZoneOffset; -import java.util.Collections; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -class AvroFormatTest { - - private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroFormat.AVRO_CONTENT_TYPE); - - // TODO - add test cases for - // - null data - // - non-bytes data - // - extension that is bytes - // - invalid extension type - @Test - void format() { - assertNotNull(format); - assertEquals(Collections.singleton("application/cloudevents+avro"), format.deserializableContentTypes()); - - CloudEvent event = CloudEventBuilder.v1() - // mandatory - .withId("") - .withSource(URI.create("")) - .withType("") - // optional - .withTime(Instant.EPOCH.atOffset(ZoneOffset.UTC)) - .withSubject("") - .withDataSchema(URI.create("")) - // extension - // support boolean, int, string, bytes - .withExtension("boolean", false) - .withExtension("int", 0) - .withExtension("string", "") - // omitting bytes, because it is not supported be CloudEvent.equals - .withData("", BytesCloudEventData.wrap(new byte[0])) - .build(); - - byte[] serialized = format.serialize(event); - - assertNotNull(serialized); - - CloudEvent deserialized = format.deserialize(serialized); - - assertEquals(event, deserialized); - - } -} diff --git a/pom.xml b/pom.xml index 457c9d95a..82c5fed7d 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ api core - formats/avro + formats/avro-turbo formats/json-jackson formats/protobuf formats/xml From aafa4031211802fbfda4f9f1fbe26c3ea29a21d5 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:07:02 -0700 Subject: [PATCH 14/28] ok Signed-off-by: Alex Collins --- README.md | 2 +- docs/avro.md | 20 +++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 30e2984e1..b07fecdab 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Javadocs are available on [javadoc.io](https://www.javadoc.io): - [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api) - [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core) -- [cloudevents-avro](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro) +- [cloudevents-avro-turbo](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-turbo) - [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson) - [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf) - [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml) diff --git a/docs/avro.md b/docs/avro.md index c9920dff5..54e4689b3 100644 --- a/docs/avro.md +++ b/docs/avro.md @@ -1,15 +1,13 @@ --- -title: CloudEvents Avro +title: CloudEvents Avro Turbo nav_order: 4 --- -# CloudEvents Avro +# CloudEvents Avro Turbo -[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-avro.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro) +[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-avro-turbo.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-turbo) -This module provides the Avro Buffer (avro) `EventFormat` implementation using the Java -avro runtime and classes generated from the CloudEvents -[avro spec](https://github.com/cloudevents/spec/blob/main/spec.avro). +This module provides the Avro Turbo `EventFormat` implementation. # Setup For Maven based projects, use the following dependency: @@ -17,14 +15,14 @@ For Maven based projects, use the following dependency: ```xml io.cloudevents - cloudevents-avro + cloudevents-avro-turbo x.y.z ``` No further configuration is required is use the module. -## Using the Avro Event Format +## Using the Avro Turbo Event Format ### Event serialization @@ -32,7 +30,7 @@ No further configuration is required is use the module. import io.cloudevents.CloudEvent; import io.cloudevents.core.format.EventFormatProvider; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.avro.avroFormat; +import io.cloudevents.avro.avroturbo.AvroTurboFormat; CloudEvent event = CloudEventBuilder.v1() .withId("hello") @@ -42,10 +40,10 @@ CloudEvent event = CloudEventBuilder.v1() byte[]serialized = EventFormatProvider .getInstance() - .resolveFormat(AvroFormat.CONTENT_TYPE) + .resolveFormat(AvroTurboFormat.CONTENT_TYPE) .serialize(event); ``` -The `EventFormatProvider` will automatically resolve the `avroFormat` using the +The `EventFormatProvider` will automatically resolve the format using the `ServiceLoader` APIs. From 659bfe9b0d2f337c8836d39f87c4f7c515e8f153 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:08:37 -0700 Subject: [PATCH 15/28] ok Signed-off-by: Alex Collins --- formats/avro-turbo/README.md | 5 ++--- .../main/java/io/cloudevents/avroturbo/AvroTurboFormat.java | 6 +++--- .../test/java/io/cloudevents/avro/AvroTurboFormatTest.java | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/formats/avro-turbo/README.md b/formats/avro-turbo/README.md index 81ad02619..fdabc1b39 100644 --- a/formats/avro-turbo/README.md +++ b/formats/avro-turbo/README.md @@ -1,9 +1,8 @@ -# CloudEvents Avro Format +# CloudEvents Avro Turbo Format This project provides functionality for the Java SDK to handle the -[avro format](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/avro-format.md). +[avro turbo format](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/avro-turbo-format.md). The Avro definition file is located in src/main/avro/spec.proto. The file was directly copied from [https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/cloudevents.avsc](). -The namespace has been changed so it does not clash with the core namespace. diff --git a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java index 2ae8ef9dd..683098d9e 100644 --- a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java +++ b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java @@ -35,12 +35,12 @@ /** * An implementation of {@link EventFormat} for the Avro format. - * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_CONTENT_TYPE}. + * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_TURBO_CONTENT_TYPE}. * It only supports data that is bytes. */ public class AvroTurboFormat implements EventFormat { - public static final String AVRO_CONTENT_TYPE = "application/cloudevents+avroturbo"; + public static final String AVRO_TURBO_CONTENT_TYPE = "application/cloudevents+avroturbo"; @Override public byte[] serialize(CloudEvent from) throws EventSerializationException { @@ -122,6 +122,6 @@ else if (value instanceof ByteBuffer) @Override public String serializedContentType() { - return AVRO_CONTENT_TYPE; + return AVRO_TURBO_CONTENT_TYPE; } } diff --git a/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java b/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java index 48d2057e7..287f6dd71 100644 --- a/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java +++ b/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java @@ -34,7 +34,7 @@ class AvroTurboFormatTest { - private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroTurboFormat.AVRO_CONTENT_TYPE); + private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroTurboFormat.AVRO_TURBO_CONTENT_TYPE); // TODO - add test cases for // - null data From 6a36420d7ea8b710b3e2214d06eed6afd1ebed8a Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:10:24 -0700 Subject: [PATCH 16/28] ok Signed-off-by: Alex Collins --- formats/avro-turbo/README.md | 8 -------- 1 file changed, 8 deletions(-) delete mode 100644 formats/avro-turbo/README.md diff --git a/formats/avro-turbo/README.md b/formats/avro-turbo/README.md deleted file mode 100644 index fdabc1b39..000000000 --- a/formats/avro-turbo/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# CloudEvents Avro Turbo Format - -This project provides functionality for the Java SDK to handle the -[avro turbo format](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/avro-turbo-format.md). - -The Avro definition file is located in src/main/avro/spec.proto. The file was directly -copied from [https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/formats/cloudevents.avsc](). - From 846d64cb85ee7e12b31d294041b3557a48571f15 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:11:01 -0700 Subject: [PATCH 17/28] ok Signed-off-by: Alex Collins --- .../main/java/io/cloudevents/avroturbo/AvroTurboFormat.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java index 683098d9e..8b7c6b368 100644 --- a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java +++ b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java @@ -34,9 +34,8 @@ import java.util.Map; /** - * An implementation of {@link EventFormat} for the Avro format. + * An implementation of {@link EventFormat} for the Avro Turbo format. * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_TURBO_CONTENT_TYPE}. - * It only supports data that is bytes. */ public class AvroTurboFormat implements EventFormat { From 5842be24554fe8ccf73d41a9bc77ce91a1453567 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:11:25 -0700 Subject: [PATCH 18/28] ok Signed-off-by: Alex Collins --- docs/index.md | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/index.md b/docs/index.md index 4799df08d..d8ec2ebe9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,25 +27,23 @@ Using the Java SDK you can: ## Supported features | | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) | -|:--------------------------------------------------:|:-----------------------------------------------------:|:-----------------------------------------------------:| +| :------------------------------------------------: | :---------------------------------------------------: | :---------------------------------------------------: | | CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: | | AMQP Protocol Binding | :x: | :x: | | - [Proton](amqp-proton.md) | :heavy_check_mark: | :heavy_check_mark: | -| AVRO Event Format | :x: | :heavy_check_mark: | +| AVRO Event Format | :x: | :x: | | HTTP Protocol Binding | :heavy_check_mark: | :heavy_check_mark: | | - [Vert.x](http-vertx.md) | :heavy_check_mark: | :heavy_check_mark: | | - [Jakarta Restful WS](http-jakarta-restful-ws.md) | :heavy_check_mark: | :heavy_check_mark: | | - [Basic](http-basic.md) | :heavy_check_mark: | :heavy_check_mark: | | - [Spring](spring.md) | :heavy_check_mark: | :heavy_check_mark: | -| - [http4k][http4k] | :heavy_check_mark: | :heavy_check_mark: | +| - [http4k][http4k] | :heavy_check_mark: | :heavy_check_mark: | | JSON Event Format | :heavy_check_mark: | :heavy_check_mark: | -| - [Avro](avro.md) | :x: | :heavy_check_mark: | -| Avro Event Format | :x: | :heavy_check_mark: | | - [Jackson](json-jackson.md) | :heavy_check_mark: | :heavy_check_mark: | -| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: | -| - [Proto](protobuf.md) | :heavy_check_mark: | :heavy_check_mark: | -| XML Event Format | :heavy_check_mark: | :heavy_check_mark: | -| - [XML](xml.md) | :heavy_check_mark: | :heavy_check_mark: | +| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: | +| - [Proto](protobuf.md) | :heavy_check_mark: | :heavy_check_mark: | +| XML Event Format | :heavy_check_mark: | :heavy_check_mark: | +| - [XML](xml.md) | :heavy_check_mark: | :heavy_check_mark: | | [Kafka Protocol Binding](kafka.md) | :heavy_check_mark: | :heavy_check_mark: | | MQTT Protocol Binding | :x: | :x: | | NATS Protocol Binding | :x: | :x: | @@ -96,8 +94,6 @@ a different feature from the different sub specs of - [`cloudevents-bom`] Module providing a [bill of materials (BOM)](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#bill-of-materials-bom-poms) for easier integration of CloudEvents in other projects -- [`cloudevents-avro`] Implementation of [Avro Event format] using - [Apache Avro](https://avro.apache.org/) - [`cloudevents-json-jackson`] Implementation of [JSON Event format] with [Jackson](https://github.com/FasterXML/jackson) - [`cloudevents-protobuf`] Implementation of [Protobuf Event format] using code generated @@ -120,7 +116,6 @@ a different feature from the different sub specs of You can look at the latest published artifacts on [Maven Central](https://search.maven.org/search?q=g:io.cloudevents). -[Avro Event format]: https://github.com/cloudevents/spec/blob/main/avro-format.md [JSON Event format]: https://github.com/cloudevents/spec/blob/v1.0/json-format.md [Protobuf Event format]: https://github.com/cloudevents/spec/blob/v1.0.1/protobuf-format.md [HTTP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md @@ -129,7 +124,6 @@ You can look at the latest published artifacts on [`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/main/api [`cloudevents-bom`]: https://github.com/cloudevents/sdk-java/tree/main/bom [`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/main/core -[`cloudevents-avro`]: https://github.com/cloudevents/sdk-java/tree/main/formats/avro [`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/main/formats/json-jackson [`cloudevents-protobuf`]: https://github.com/cloudevents/sdk-java/tree/main/formats/protobuf [`cloudevents-xml`]: https://github.com/cloudevents/sdk-java/tree/main/formats/xml From e9397bdb57613a7339917ab1f5b52dfa0009fb42 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 08:14:17 -0700 Subject: [PATCH 19/28] ok Signed-off-by: Alex Collins --- formats/avro-turbo/src/main/avro/cloudevents.avsc | 13 +++++++------ .../io/cloudevents/avroturbo/AvroTurboFormat.java | 14 +++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/formats/avro-turbo/src/main/avro/cloudevents.avsc b/formats/avro-turbo/src/main/avro/cloudevents.avsc index 53bf74884..3ad701462 100644 --- a/formats/avro-turbo/src/main/avro/cloudevents.avsc +++ b/formats/avro-turbo/src/main/avro/cloudevents.avsc @@ -1,9 +1,9 @@ { - "namespace": "io.cloudevents.v1.avro", + "namespace": "io.cloudevents.v1.avroturbo", "type": "record", "name": "CloudEvent", "version": "1.0", - "doc": "Avro Event Format for CloudEvents", + "doc": "Avro Turbo Event Format for CloudEvents", "fields": [ { "name": "id", @@ -46,25 +46,26 @@ "type": [ "null", { - "type": "long", - "logicalType": "timestamp-millis" + "type": "long", + "logicalType": "timestamp-micros" } ], "default": null }, { - "name": "attribute", + "name": "attributes", "type": { "type": "map", "values": [ "null", "boolean", "int", + "long", "string", "bytes" ] }, - "default": {} + "default": {} }, { "name": "data", diff --git a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java index 8b7c6b368..b1e13df4e 100644 --- a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java +++ b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java @@ -25,7 +25,7 @@ import io.cloudevents.core.format.EventFormat; import io.cloudevents.core.format.EventSerializationException; import io.cloudevents.rw.CloudEventDataMapper; -import io.cloudevents.v1.avro.CloudEvent.Builder; +import io.cloudevents.v1.avroturbo.CloudEvent.Builder; import java.net.URI; import java.nio.ByteBuffer; @@ -44,13 +44,13 @@ public class AvroTurboFormat implements EventFormat { @Override public byte[] serialize(CloudEvent from) throws EventSerializationException { try { - Builder to = io.cloudevents.v1.avro.CloudEvent.newBuilder(); + Builder to = io.cloudevents.v1.avroturbo.CloudEvent.newBuilder(); // extensions - Map attribute = new HashMap<>(); + Map attributes = new HashMap<>(); for (String name : from.getExtensionNames()) { Object value = from.getExtension(name); - attribute.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); + attributes.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); } to.setSource(from.getSource().toString()) @@ -58,7 +58,7 @@ public byte[] serialize(CloudEvent from) throws EventSerializationException { .setId(from.getId()) .setSubject(from.getSubject()) .setDatacontenttype(from.getDataContentType()) - .setAttribute(attribute); + .setAttributes(attributes); if (from.getTime() != null) to.setTime(from.getTime().toInstant()); @@ -77,7 +77,7 @@ public byte[] serialize(CloudEvent from) throws EventSerializationException { @Override public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) throws EventDeserializationException { try { - io.cloudevents.v1.avro.CloudEvent from = io.cloudevents.v1.avro.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); + io.cloudevents.v1.avroturbo.CloudEvent from = io.cloudevents.v1.avroturbo.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); CloudEventBuilder to = CloudEventBuilder.v1() .withSource(URI.create(from.getSource())) .withType(from.getType()) @@ -91,7 +91,7 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper entry : from.getAttribute().entrySet()) { + for (Map.Entry entry : from.getAttributes().entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); // Avro supports boolean, int, string, bytes From d2149f977cea5e4c59cb902c70db657cddd6c003 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 6 Jun 2023 17:34:38 -0700 Subject: [PATCH 20/28] ok Signed-off-by: Alex Collins --- .../src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java index b1e13df4e..b54075662 100644 --- a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java +++ b/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java @@ -99,6 +99,8 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper Date: Tue, 20 Jun 2023 20:29:07 -0700 Subject: [PATCH 21/28] turbo -> compact Signed-off-by: Alex Collins --- README.md | 2 +- docs/avro.md | 16 ++++++++-------- formats/{avro-turbo => avro-compact}/pom.xml | 6 +++--- .../src/main/avro/cloudevents-compact.avsc} | 6 +++--- .../avro/compact/AvroCompactFormat.java} | 18 +++++++++--------- .../io.cloudevents.core.format.EventFormat | 1 + .../avro/compact/AvroCompactFormatTest.java} | 9 ++++----- .../io.cloudevents.core.format.EventFormat | 1 - pom.xml | 2 +- 9 files changed, 30 insertions(+), 31 deletions(-) rename formats/{avro-turbo => avro-compact}/pom.xml (95%) rename formats/{avro-turbo/src/main/avro/cloudevents.avsc => avro-compact/src/main/avro/cloudevents-compact.avsc} (91%) rename formats/{avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java => avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java} (87%) create mode 100644 formats/avro-compact/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat rename formats/{avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java => avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java} (91%) delete mode 100644 formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat diff --git a/README.md b/README.md index b07fecdab..c786ce0e6 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ Javadocs are available on [javadoc.io](https://www.javadoc.io): - [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api) - [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core) -- [cloudevents-avro-turbo](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-turbo) +- [cloudevents-avro-compact](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact) - [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson) - [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf) - [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml) diff --git a/docs/avro.md b/docs/avro.md index 54e4689b3..0a029edc3 100644 --- a/docs/avro.md +++ b/docs/avro.md @@ -1,13 +1,13 @@ --- -title: CloudEvents Avro Turbo +title: CloudEvents Avro Compact nav_order: 4 --- -# CloudEvents Avro Turbo +# CloudEvents Avro Compact -[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-avro-turbo.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-turbo) +[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-avro-compact.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact) -This module provides the Avro Turbo `EventFormat` implementation. +This module provides the Avro Compact `EventFormat` implementation. # Setup For Maven based projects, use the following dependency: @@ -15,14 +15,14 @@ For Maven based projects, use the following dependency: ```xml io.cloudevents - cloudevents-avro-turbo + cloudevents-avro-compact x.y.z ``` No further configuration is required is use the module. -## Using the Avro Turbo Event Format +## Using the Avro Compact Event Format ### Event serialization @@ -30,7 +30,7 @@ No further configuration is required is use the module. import io.cloudevents.CloudEvent; import io.cloudevents.core.format.EventFormatProvider; import io.cloudevents.core.builder.CloudEventBuilder; -import io.cloudevents.avro.avroturbo.AvroTurboFormat; +import io.cloudevents.avro.avro.compact.AvroCompactFormat; CloudEvent event = CloudEventBuilder.v1() .withId("hello") @@ -40,7 +40,7 @@ CloudEvent event = CloudEventBuilder.v1() byte[]serialized = EventFormatProvider .getInstance() - .resolveFormat(AvroTurboFormat.CONTENT_TYPE) + .resolveFormat(AvroCompactFormat.CONTENT_TYPE) .serialize(event); ``` diff --git a/formats/avro-turbo/pom.xml b/formats/avro-compact/pom.xml similarity index 95% rename from formats/avro-turbo/pom.xml rename to formats/avro-compact/pom.xml index 8d7560c1b..e79955755 100644 --- a/formats/avro-turbo/pom.xml +++ b/formats/avro-compact/pom.xml @@ -27,13 +27,13 @@ ../../pom.xml - cloudevents-avro-turbo - CloudEvents - Avro Turbo + cloudevents-avro-compact + CloudEvents - Avro Compact 2.13.3 - io.cloudevents.formats.avroturbo + io.cloudevents.formats.avro.compact diff --git a/formats/avro-turbo/src/main/avro/cloudevents.avsc b/formats/avro-compact/src/main/avro/cloudevents-compact.avsc similarity index 91% rename from formats/avro-turbo/src/main/avro/cloudevents.avsc rename to formats/avro-compact/src/main/avro/cloudevents-compact.avsc index 3ad701462..9154b04a4 100644 --- a/formats/avro-turbo/src/main/avro/cloudevents.avsc +++ b/formats/avro-compact/src/main/avro/cloudevents-compact.avsc @@ -1,9 +1,9 @@ { - "namespace": "io.cloudevents.v1.avroturbo", + "namespace": "io.cloudevents.v1.avro.compact", "type": "record", "name": "CloudEvent", "version": "1.0", - "doc": "Avro Turbo Event Format for CloudEvents", + "doc": "Avro Compact Event Format for CloudEvents", "fields": [ { "name": "id", @@ -76,4 +76,4 @@ "default": "null" } ] -} \ No newline at end of file +} diff --git a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java b/formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java similarity index 87% rename from formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java rename to formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java index b54075662..2747dec08 100644 --- a/formats/avro-turbo/src/main/java/io/cloudevents/avroturbo/AvroTurboFormat.java +++ b/formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java @@ -14,7 +14,7 @@ * limitations under the License. * */ -package io.cloudevents.avroturbo; +package io.cloudevents.avro.compact; import io.cloudevents.CloudEvent; @@ -25,7 +25,7 @@ import io.cloudevents.core.format.EventFormat; import io.cloudevents.core.format.EventSerializationException; import io.cloudevents.rw.CloudEventDataMapper; -import io.cloudevents.v1.avroturbo.CloudEvent.Builder; +import io.cloudevents.v1.avro.compact.CloudEvent.Builder; import java.net.URI; import java.nio.ByteBuffer; @@ -34,17 +34,17 @@ import java.util.Map; /** - * An implementation of {@link EventFormat} for the Avro Turbo format. - * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_TURBO_CONTENT_TYPE}. + * An implementation of {@link EventFormat} for the Avro Compact format. + * This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_COMPACT_CONTENT_TYPE}. */ -public class AvroTurboFormat implements EventFormat { +public class AvroCompactFormat implements EventFormat { - public static final String AVRO_TURBO_CONTENT_TYPE = "application/cloudevents+avroturbo"; + public static final String AVRO_COMPACT_CONTENT_TYPE = "application/cloudevents+avrocompact"; @Override public byte[] serialize(CloudEvent from) throws EventSerializationException { try { - Builder to = io.cloudevents.v1.avroturbo.CloudEvent.newBuilder(); + Builder to = io.cloudevents.v1.avro.compact.CloudEvent.newBuilder(); // extensions Map attributes = new HashMap<>(); @@ -77,7 +77,7 @@ public byte[] serialize(CloudEvent from) throws EventSerializationException { @Override public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) throws EventDeserializationException { try { - io.cloudevents.v1.avroturbo.CloudEvent from = io.cloudevents.v1.avroturbo.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); + io.cloudevents.v1.avro.compact.CloudEvent from = io.cloudevents.v1.avro.compact.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes)); CloudEventBuilder to = CloudEventBuilder.v1() .withSource(URI.create(from.getSource())) .withType(from.getType()) @@ -123,6 +123,6 @@ else if (value instanceof ByteBuffer) @Override public String serializedContentType() { - return AVRO_TURBO_CONTENT_TYPE; + return AVRO_COMPACT_CONTENT_TYPE; } } diff --git a/formats/avro-compact/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat b/formats/avro-compact/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat new file mode 100644 index 000000000..ae558f090 --- /dev/null +++ b/formats/avro-compact/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat @@ -0,0 +1 @@ +io.cloudevents.avro.compact.AvroCompactFormat diff --git a/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java b/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java similarity index 91% rename from formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java rename to formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java index 287f6dd71..89f373d72 100644 --- a/formats/avro-turbo/src/test/java/io/cloudevents/avro/AvroTurboFormatTest.java +++ b/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java @@ -14,10 +14,9 @@ * limitations under the License. * */ -package io.cloudevents.avro; +package io.cloudevents.avro.compact; import io.cloudevents.CloudEvent; -import io.cloudevents.avroturbo.AvroTurboFormat; import io.cloudevents.core.builder.CloudEventBuilder; import io.cloudevents.core.data.BytesCloudEventData; import io.cloudevents.core.format.EventFormat; @@ -32,9 +31,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -class AvroTurboFormatTest { +class AvroCompactFormatTest { - private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroTurboFormat.AVRO_TURBO_CONTENT_TYPE); + private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroCompactFormat.AVRO_COMPACT_CONTENT_TYPE); // TODO - add test cases for // - null data @@ -44,7 +43,7 @@ class AvroTurboFormatTest { @Test void format() { assertNotNull(format); - assertEquals(Collections.singleton("application/cloudevents+avroturbo"), format.deserializableContentTypes()); + assertEquals(Collections.singleton("application/cloudevents+avrocompact"), format.deserializableContentTypes()); CloudEvent event = CloudEventBuilder.v1() // mandatory diff --git a/formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat b/formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat deleted file mode 100644 index 4274487db..000000000 --- a/formats/avro-turbo/src/main/resources/META-INF/services/io.cloudevents.core.format.EventFormat +++ /dev/null @@ -1 +0,0 @@ -io.cloudevents.avroturbo.AvroTurboFormat diff --git a/pom.xml b/pom.xml index 82c5fed7d..252f9d4c3 100644 --- a/pom.xml +++ b/pom.xml @@ -68,7 +68,7 @@ api core - formats/avro-turbo + formats/avro-compact formats/json-jackson formats/protobuf formats/xml From 9d70377db479e0cdfa8f014f42c151fc986fe6e8 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Tue, 20 Jun 2023 20:33:27 -0700 Subject: [PATCH 22/28] ok Signed-off-by: Alex Collins --- .../avro/compact/AvroCompactFormatTest.java | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java b/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java index 89f373d72..70507c601 100644 --- a/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java +++ b/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java @@ -35,11 +35,6 @@ class AvroCompactFormatTest { private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroCompactFormat.AVRO_COMPACT_CONTENT_TYPE); - // TODO - add test cases for - // - null data - // - non-bytes data - // - extension that is bytes - // - invalid extension type @Test void format() { assertNotNull(format); @@ -55,11 +50,12 @@ void format() { .withSubject("") .withDataSchema(URI.create("")) // extension - // support boolean, int, string, bytes + // support boolean, int, long, string, bytes .withExtension("boolean", false) .withExtension("int", 0) + .withExtension("long", 0L) .withExtension("string", "") - // omitting bytes, because it is not supported be CloudEvent.equals + // omitting bytes, because it is not supported by CloudEvent.equals .withData("", BytesCloudEventData.wrap(new byte[0])) .build(); From d579575c4ade4965b94d6ea2e7f57e55a7f031ce Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Thu, 29 Jun 2023 08:37:10 -0700 Subject: [PATCH 23/28] ok Signed-off-by: Alex Collins --- .../src/main/avro/cloudevents-compact.avsc | 8 +++++--- .../avro/compact/AvroCompactFormat.java | 18 ++++++++++++------ .../avro/compact/AvroCompactFormatTest.java | 2 +- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/formats/avro-compact/src/main/avro/cloudevents-compact.avsc b/formats/avro-compact/src/main/avro/cloudevents-compact.avsc index 9154b04a4..f875781dc 100644 --- a/formats/avro-compact/src/main/avro/cloudevents-compact.avsc +++ b/formats/avro-compact/src/main/avro/cloudevents-compact.avsc @@ -53,14 +53,16 @@ "default": null }, { - "name": "attributes", + "name": "extensions", "type": { "type": "map", "values": [ - "null", "boolean", "int", - "long", + { + "type": "long", + "logicalType" : "timestamp-micros" + }, "string", "bytes" ] diff --git a/formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java b/formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java index 2747dec08..066776414 100644 --- a/formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java +++ b/formats/avro-compact/src/main/java/io/cloudevents/avro/compact/AvroCompactFormat.java @@ -29,6 +29,8 @@ import java.net.URI; import java.nio.ByteBuffer; +import java.time.Instant; +import java.time.OffsetDateTime; import java.time.ZoneOffset; import java.util.HashMap; import java.util.Map; @@ -47,10 +49,14 @@ public byte[] serialize(CloudEvent from) throws EventSerializationException { Builder to = io.cloudevents.v1.avro.compact.CloudEvent.newBuilder(); // extensions - Map attributes = new HashMap<>(); + Map extensions = new HashMap<>(); for (String name : from.getExtensionNames()) { Object value = from.getExtension(name); - attributes.put(name, value instanceof byte[] ? ByteBuffer.wrap((byte[]) value) : value); + if (value instanceof byte[]) + value = ByteBuffer.wrap((byte[]) value); + else if (value instanceof OffsetDateTime) + value = ((OffsetDateTime) value).toInstant(); + extensions.put(name, value); } to.setSource(from.getSource().toString()) @@ -58,7 +64,7 @@ public byte[] serialize(CloudEvent from) throws EventSerializationException { .setId(from.getId()) .setSubject(from.getSubject()) .setDatacontenttype(from.getDataContentType()) - .setAttributes(attributes); + .setExtensions(extensions); if (from.getTime() != null) to.setTime(from.getTime().toInstant()); @@ -91,7 +97,7 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper entry : from.getAttributes().entrySet()) { + for (Map.Entry entry : from.getExtensions().entrySet()) { String name = entry.getKey(); Object value = entry.getValue(); // Avro supports boolean, int, string, bytes @@ -99,8 +105,8 @@ public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper Date: Fri, 7 Jul 2023 08:20:14 -0700 Subject: [PATCH 24/28] bump Avro version Signed-off-by: Alex Collins Signed-off-by: Alex Collins --- formats/avro-compact/pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/formats/avro-compact/pom.xml b/formats/avro-compact/pom.xml index e79955755..ef02e08ee 100644 --- a/formats/avro-compact/pom.xml +++ b/formats/avro-compact/pom.xml @@ -41,7 +41,7 @@ org.apache.avro avro-maven-plugin - 1.11.1 + 1.11.2 String @@ -66,7 +66,7 @@ org.apache.avro avro - 1.11.1 + 1.11.2 From 4a759211c946a156191bca8e0706e8a63b9c32b1 Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 14 Jul 2023 11:01:28 +0100 Subject: [PATCH 25/28] Update docs/avro.md Co-authored-by: Pierangelo Di Pilato Signed-off-by: Alex Collins --- docs/avro.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/avro.md b/docs/avro.md index 0a029edc3..0b98fa8f0 100644 --- a/docs/avro.md +++ b/docs/avro.md @@ -38,7 +38,7 @@ CloudEvent event = CloudEventBuilder.v1() .withSource(URI.create("http://localhost")) .build(); -byte[]serialized = EventFormatProvider +byte[] serialized = EventFormatProvider .getInstance() .resolveFormat(AvroCompactFormat.CONTENT_TYPE) .serialize(event); From 68501d3e75eec4834917ed98a0b9d518b85942bc Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 14 Jul 2023 11:01:40 +0100 Subject: [PATCH 26/28] Update formats/avro-compact/pom.xml Co-authored-by: Pierangelo Di Pilato Signed-off-by: Alex Collins --- formats/avro-compact/pom.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/formats/avro-compact/pom.xml b/formats/avro-compact/pom.xml index ef02e08ee..113f20336 100644 --- a/formats/avro-compact/pom.xml +++ b/formats/avro-compact/pom.xml @@ -74,8 +74,7 @@ org.slf4j slf4j-simple 1.7.36 - test - + test org.junit.jupiter From c080666dd3c5210cd374e7db206aeb0e829e82fb Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 14 Jul 2023 11:01:50 +0100 Subject: [PATCH 27/28] Update formats/avro-compact/pom.xml Co-authored-by: Pierangelo Di Pilato Signed-off-by: Alex Collins --- formats/avro-compact/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/formats/avro-compact/pom.xml b/formats/avro-compact/pom.xml index 113f20336..a258c7b62 100644 --- a/formats/avro-compact/pom.xml +++ b/formats/avro-compact/pom.xml @@ -32,7 +32,6 @@ - 2.13.3 io.cloudevents.formats.avro.compact From 0d853ab33571574f5c1efbb7549b8ad4a5636c1d Mon Sep 17 00:00:00 2001 From: Alex Collins Date: Fri, 14 Jul 2023 10:13:57 +0000 Subject: [PATCH 28/28] ok Signed-off-by: Alex Collins --- .../main/java/io/cloudevents/core/format/ContentType.java | 4 ++++ .../io/cloudevents/avro/compact/AvroCompactFormatTest.java | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/core/src/main/java/io/cloudevents/core/format/ContentType.java b/core/src/main/java/io/cloudevents/core/format/ContentType.java index 1d8656393..c5b19f883 100644 --- a/core/src/main/java/io/cloudevents/core/format/ContentType.java +++ b/core/src/main/java/io/cloudevents/core/format/ContentType.java @@ -45,6 +45,10 @@ public enum ContentType { * The content type for transports sending cloudevents in the protocol buffer format. */ PROTO("application/cloudevents+protobuf"), + /** + * The content type for transports sending cloudevents in the compact Avro format. + */ + AVRO_COMPACT("application/cloudevents+avrocompact"), /** * The content type for transports sending cloudevents in XML format. */ diff --git a/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java b/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java index e87657bc2..484ab15f9 100644 --- a/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java +++ b/formats/avro-compact/src/test/java/io/cloudevents/avro/compact/AvroCompactFormatTest.java @@ -28,6 +28,7 @@ import java.time.ZoneOffset; import java.util.Collections; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -67,5 +68,10 @@ void format() { assertEquals(event, deserialized); + byte[] reserialized = format.serialize(deserialized); + + assertArrayEquals(serialized, reserialized); + + } }