Skip to content

Commit 4ef3041

Browse files
authored
[working-draft] Support Avro Compact Format (#578)
Add support for working-draft spec Avro compact format: cloudevents/spec@777d0c0 Signed-off-by: Alex Collins <[email protected]>
1 parent 582feed commit 4ef3041

File tree

9 files changed

+449
-0
lines changed

9 files changed

+449
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ Javadocs are available on [javadoc.io](https://www.javadoc.io):
6363

6464
- [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
6565
- [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
66+
- [cloudevents-avro-compact](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact)
6667
- [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
6768
- [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
6869
- [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml)

core/src/main/java/io/cloudevents/core/format/ContentType.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ public enum ContentType {
4545
* The content type for transports sending cloudevents in the protocol buffer format.
4646
*/
4747
PROTO("application/cloudevents+protobuf"),
48+
/**
49+
* The content type for transports sending cloudevents in the compact Avro format.
50+
*/
51+
AVRO_COMPACT("application/cloudevents+avrocompact"),
4852
/**
4953
* The content type for transports sending cloudevents in XML format.
5054
*/

docs/avro.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
title: CloudEvents Avro Compact
3+
nav_order: 4
4+
---
5+
6+
# CloudEvents Avro Compact
7+
8+
[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-avro-compact.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact)
9+
10+
This module provides the Avro Compact `EventFormat` implementation.
11+
12+
# Setup
13+
For Maven based projects, use the following dependency:
14+
15+
```xml
16+
<dependency>
17+
<groupId>io.cloudevents</groupId>
18+
<artifactId>cloudevents-avro-compact</artifactId>
19+
<version>x.y.z</version>
20+
</dependency>
21+
```
22+
23+
No further configuration is required is use the module.
24+
25+
## Using the Avro Compact Event Format
26+
27+
### Event serialization
28+
29+
```java
30+
import io.cloudevents.CloudEvent;
31+
import io.cloudevents.core.format.EventFormatProvider;
32+
import io.cloudevents.core.builder.CloudEventBuilder;
33+
import io.cloudevents.avro.avro.compact.AvroCompactFormat;
34+
35+
CloudEvent event = CloudEventBuilder.v1()
36+
.withId("hello")
37+
.withType("example.vertx")
38+
.withSource(URI.create("http://localhost"))
39+
.build();
40+
41+
byte[] serialized = EventFormatProvider
42+
.getInstance()
43+
.resolveFormat(AvroCompactFormat.CONTENT_TYPE)
44+
.serialize(event);
45+
```
46+
47+
The `EventFormatProvider` will automatically resolve the format using the
48+
`ServiceLoader` APIs.
49+

formats/avro-compact/pom.xml

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2021-Present The CloudEvents Authors
4+
~ <p>
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~ <p>
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~ <p>
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
~
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
23+
<parent>
24+
<groupId>io.cloudevents</groupId>
25+
<artifactId>cloudevents-parent</artifactId>
26+
<version>3.0.0-SNAPSHOT</version>
27+
<relativePath>../../pom.xml</relativePath>
28+
</parent>
29+
30+
<artifactId>cloudevents-avro-compact</artifactId>
31+
<name>CloudEvents - Avro Compact</name>
32+
33+
34+
<properties>
35+
<module-name>io.cloudevents.formats.avro.compact</module-name>
36+
</properties>
37+
38+
<build>
39+
<plugins>
40+
<plugin>
41+
<groupId>org.apache.avro</groupId>
42+
<artifactId>avro-maven-plugin</artifactId>
43+
<version>1.11.2</version>
44+
<configuration>
45+
<stringType>String</stringType>
46+
</configuration>
47+
<executions>
48+
<execution>
49+
<phase>generate-sources</phase>
50+
<goals>
51+
<goal>schema</goal>
52+
</goals>
53+
</execution>
54+
</executions>
55+
</plugin>
56+
</plugins>
57+
</build>
58+
59+
<dependencies>
60+
<dependency>
61+
<groupId>io.cloudevents</groupId>
62+
<artifactId>cloudevents-core</artifactId>
63+
<version>${project.version}</version>
64+
</dependency>
65+
<dependency>
66+
<groupId>org.apache.avro</groupId>
67+
<artifactId>avro</artifactId>
68+
<version>1.11.2</version>
69+
</dependency>
70+
71+
<!-- Test deps -->
72+
<dependency>
73+
<groupId>org.slf4j</groupId>
74+
<artifactId>slf4j-simple</artifactId>
75+
<version>1.7.36</version>
76+
<scope>test</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>org.junit.jupiter</groupId>
80+
<artifactId>junit-jupiter</artifactId>
81+
<version>${junit-jupiter.version}</version>
82+
<scope>test</scope>
83+
</dependency>
84+
<dependency>
85+
<groupId>org.assertj</groupId>
86+
<artifactId>assertj-core</artifactId>
87+
<version>${assertj-core.version}</version>
88+
<scope>test</scope>
89+
</dependency>
90+
<dependency>
91+
<groupId>io.cloudevents</groupId>
92+
<artifactId>cloudevents-core</artifactId>
93+
<classifier>tests</classifier>
94+
<type>test-jar</type>
95+
<version>${project.version}</version>
96+
<scope>test</scope>
97+
</dependency>
98+
99+
</dependencies>
100+
101+
</project>
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
{
2+
"namespace": "io.cloudevents.v1.avro.compact",
3+
"type": "record",
4+
"name": "CloudEvent",
5+
"version": "1.0",
6+
"doc": "Avro Compact Event Format for CloudEvents",
7+
"fields": [
8+
{
9+
"name": "id",
10+
"type": "string"
11+
},
12+
{
13+
"name": "source",
14+
"type": "string"
15+
},
16+
{
17+
"name": "type",
18+
"type": "string"
19+
},
20+
{
21+
"name": "datacontenttype",
22+
"type": [
23+
"null",
24+
"string"
25+
],
26+
"default": null
27+
},
28+
{
29+
"name": "dataschema",
30+
"type": [
31+
"null",
32+
"string"
33+
],
34+
"default": null
35+
},
36+
{
37+
"name": "subject",
38+
"type": [
39+
"null",
40+
"string"
41+
],
42+
"default": null
43+
},
44+
{
45+
"name": "time",
46+
"type": [
47+
"null",
48+
{
49+
"type": "long",
50+
"logicalType": "timestamp-micros"
51+
}
52+
],
53+
"default": null
54+
},
55+
{
56+
"name": "extensions",
57+
"type": {
58+
"type": "map",
59+
"values": [
60+
"boolean",
61+
"int",
62+
{
63+
"type": "long",
64+
"logicalType" : "timestamp-micros"
65+
},
66+
"string",
67+
"bytes"
68+
]
69+
},
70+
"default": {}
71+
},
72+
{
73+
"name": "data",
74+
"type": [
75+
"bytes",
76+
"null"
77+
],
78+
"default": "null"
79+
}
80+
]
81+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright 2018-Present The CloudEvents Authors
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package io.cloudevents.avro.compact;
18+
19+
20+
import io.cloudevents.CloudEvent;
21+
import io.cloudevents.CloudEventData;
22+
import io.cloudevents.core.builder.CloudEventBuilder;
23+
import io.cloudevents.core.data.BytesCloudEventData;
24+
import io.cloudevents.core.format.EventDeserializationException;
25+
import io.cloudevents.core.format.EventFormat;
26+
import io.cloudevents.core.format.EventSerializationException;
27+
import io.cloudevents.rw.CloudEventDataMapper;
28+
import io.cloudevents.v1.avro.compact.CloudEvent.Builder;
29+
30+
import java.net.URI;
31+
import java.nio.ByteBuffer;
32+
import java.time.Instant;
33+
import java.time.OffsetDateTime;
34+
import java.time.ZoneOffset;
35+
import java.util.HashMap;
36+
import java.util.Map;
37+
38+
/**
39+
* An implementation of {@link EventFormat} for the Avro Compact format.
40+
* This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_COMPACT_CONTENT_TYPE}.
41+
*/
42+
public class AvroCompactFormat implements EventFormat {
43+
44+
public static final String AVRO_COMPACT_CONTENT_TYPE = "application/cloudevents+avrocompact";
45+
46+
@Override
47+
public byte[] serialize(CloudEvent from) throws EventSerializationException {
48+
try {
49+
Builder to = io.cloudevents.v1.avro.compact.CloudEvent.newBuilder();
50+
51+
// extensions
52+
Map<String, Object> extensions = new HashMap<>();
53+
for (String name : from.getExtensionNames()) {
54+
Object value = from.getExtension(name);
55+
if (value instanceof byte[])
56+
value = ByteBuffer.wrap((byte[]) value);
57+
else if (value instanceof OffsetDateTime)
58+
value = ((OffsetDateTime) value).toInstant();
59+
extensions.put(name, value);
60+
}
61+
62+
to.setSource(from.getSource().toString())
63+
.setType(from.getType())
64+
.setId(from.getId())
65+
.setSubject(from.getSubject())
66+
.setDatacontenttype(from.getDataContentType())
67+
.setExtensions(extensions);
68+
69+
if (from.getTime() != null)
70+
to.setTime(from.getTime().toInstant());
71+
if (from.getDataSchema() != null)
72+
to.setDataschema(from.getDataSchema().toString());
73+
74+
CloudEventData data = from.getData();
75+
if (data != null)
76+
to.setData(ByteBuffer.wrap(data.toBytes()));
77+
return to.build().toByteBuffer().array();
78+
} catch (Exception e) {
79+
throw new EventSerializationException(e);
80+
}
81+
}
82+
83+
@Override
84+
public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper<? extends CloudEventData> mapper) throws EventDeserializationException {
85+
try {
86+
io.cloudevents.v1.avro.compact.CloudEvent from = io.cloudevents.v1.avro.compact.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes));
87+
CloudEventBuilder to = CloudEventBuilder.v1()
88+
.withSource(URI.create(from.getSource()))
89+
.withType(from.getType())
90+
.withId(from.getType())
91+
.withSubject(from.getSubject())
92+
.withDataContentType(from.getDatacontenttype());
93+
94+
if (from.getTime() != null)
95+
to.withTime(from.getTime().atOffset(ZoneOffset.UTC));
96+
if (from.getDataschema() != null)
97+
to.withDataSchema(URI.create(from.getDataschema()));
98+
99+
// extensions
100+
for (Map.Entry<String, Object> entry : from.getExtensions().entrySet()) {
101+
String name = entry.getKey();
102+
Object value = entry.getValue();
103+
// Avro supports boolean, int, string, bytes
104+
if (value instanceof Boolean)
105+
to.withExtension(name, (boolean) value);
106+
else if (value instanceof Integer)
107+
to.withExtension(name, (int) value);
108+
else if (value instanceof Instant)
109+
to.withExtension(name, ((Instant) value).atOffset(ZoneOffset.UTC));
110+
else if (value instanceof String)
111+
to.withExtension(name, (String) value);
112+
else if (value instanceof ByteBuffer)
113+
to.withExtension(name, ((ByteBuffer) value).array());
114+
else
115+
// this cannot happen, if ever seen, must be bug in this library
116+
throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass()));
117+
}
118+
119+
if (from.getData() == null)
120+
return to.end();
121+
else {
122+
CloudEventData data = BytesCloudEventData.wrap(from.getData().array());
123+
return to.end(mapper.map(data));
124+
}
125+
} catch (Exception e) {
126+
throw new EventDeserializationException(e);
127+
}
128+
}
129+
130+
@Override
131+
public String serializedContentType() {
132+
return AVRO_COMPACT_CONTENT_TYPE;
133+
}
134+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.cloudevents.avro.compact.AvroCompactFormat

0 commit comments

Comments
 (0)