Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 52 additions & 4 deletions docs/protobuf.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,22 @@ This module provides the Protocol Buffer (protobuf) `EventFormat` implementation
Protobuf runtime and classes generated from the CloudEvents
[proto spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.proto).

# Setup
For Maven based projects, use the following dependency:

```xml
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-protobuf</artifactId>
<version>2.3.0</version>
<version>x.y.z</version>
</dependency>
```

No further configuration is required is use the module.

## Using the Protobuf Event Format

You don't need to perform any operation to configure the module, more than
adding the dependency to your project:
### Event serialization

```java
import io.cloudevents.CloudEvent;
Expand All @@ -44,5 +46,51 @@ byte[]serialized = EventFormatProvider
.serialize(event);
```

The `EventFormatProvider` will resolve automatically the `ProtobufFormat` using the
The `EventFormatProvider` will automatically resolve the `ProtobufFormat` using the
`ServiceLoader` APIs.

## Passing Protobuf messages as CloudEvent data.

The `ProtoCloudEventData` capability provides a convenience mechanism to handle Protobuf message object data.

### Building

```java
// Build my business event message.
com.google.protobuf.Message myMessage = ..... ;

// Wrap the protobuf message as CloudEventData.
CloudEventData ceData = ProtoCloudEventData.wrap(myMessage);

// Build the CloudEvent
CloudEvent event = CloudEventBuilder.v1()
.withId("hello")
.withType("example.protodata")
.withSource(URI.create("http://localhost"))
.withData(ceData)
.build();
```

### Reading

If the `ProtobufFormat` is used to deserialize a CloudEvent that contains a protobuf message object as data you can use
the `ProtoCloudEventData` 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 ProtoCloudEventData) {

// Obtain the protobuf 'any'
Any anAny = ((ProtoCloudEventData) eventData).getAny();

...
}

```

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package io.cloudevents.protobuf;

import com.google.protobuf.Any;
import com.google.protobuf.Message;
import io.cloudevents.CloudEventData;

Expand All @@ -26,13 +27,7 @@
public interface ProtoCloudEventData extends CloudEventData {

/**
* Gets the protobuf {@link Message} representation of this data.
* @return The data as a {@link Message}
*/
Message getMessage();

/**
* Convenience helper to wrap a Protobuf Message as
* Convenience helper to wrap a Protobuf {@link Message} as
* CloudEventData.
*
* @param protoMessage The message to wrap
Expand All @@ -41,4 +36,11 @@ public interface ProtoCloudEventData extends CloudEventData {
static CloudEventData wrap(Message protoMessage) {
return new ProtoDataWrapper(protoMessage);
}

/**
* Gets the protobuf {@link Any} representation of this data.
*
* @return The data as an {@link Any}
*/
Any getAny();
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,31 @@
import com.google.protobuf.Any;
import com.google.protobuf.Message;

import java.util.Arrays;
import java.util.Objects;

class ProtoDataWrapper implements ProtoCloudEventData {

private final Message protoMessage;
private final Any protoAny;

ProtoDataWrapper(Message protoMessage) {
this.protoMessage = protoMessage;

Objects.requireNonNull(protoMessage);

if (protoMessage instanceof Any) {
protoAny = (Any) protoMessage;
} else {
protoAny = Any.pack(protoMessage);
}
}

@Override
public Message getMessage() {
return protoMessage;
public Any getAny() {
return protoAny;
}

@Override
public byte[] toBytes() {
return protoMessage.toByteArray();
return protoAny.toByteArray();
}

@Override
Expand All @@ -53,17 +60,21 @@ public boolean equals(Object obj) {
// Now compare the actual data
ProtoDataWrapper rhs = (ProtoDataWrapper) obj;

if (this.getMessage() == rhs.getMessage()){
return true;
}
final Any lhsAny = getAny();
final Any rhsAny = rhs.getAny();

// This is split out for readability.
// Compare the content in terms onf an 'Any'.
// - Verify the types match
// - Verify the values match.
// 1. Sanity compare the 'Any' references.
// 2. Compare the content in terms onf an 'Any'.
// - Verify the types match
// - Verify the values match.

final Any lhsAny = getAsAny(this.getMessage());
final Any rhsAny = getAsAny(rhs.getMessage());
// NULL checks not required as object cannot be built
// with a null.

if (lhsAny == rhsAny) {
return true;
}

final boolean typesMatch = (ProtoSupport.extractMessageType(lhsAny).equals(ProtoSupport.extractMessageType(rhsAny)));

Expand All @@ -72,15 +83,7 @@ public boolean equals(Object obj) {
} else {
return false;
}
}

private Any getAsAny(Message m) {

if (m instanceof Any) {
return (Any) m;
}

return Any.pack(m);

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@
*/
package io.cloudevents.protobuf;

import com.google.protobuf.Message;
import io.cloudevents.CloudEventData;
import io.cloudevents.SpecVersion;
import io.cloudevents.core.data.BytesCloudEventData;
import io.cloudevents.core.v1.CloudEventV1;
import io.cloudevents.rw.CloudEventDataMapper;
import io.cloudevents.rw.CloudEventRWException;
import io.cloudevents.rw.CloudEventReader;
import io.cloudevents.rw.CloudEventWriter;
import io.cloudevents.rw.CloudEventWriterFactory;
import io.cloudevents.rw.*;
import io.cloudevents.v1.proto.CloudEvent;
import io.cloudevents.v1.proto.CloudEvent.CloudEventAttributeValue;

import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,11 @@
*/
package io.cloudevents.protobuf;

import com.google.protobuf.*;
import com.google.protobuf.Any;
import com.google.protobuf.ByteString;
import com.google.protobuf.Descriptors.FieldDescriptor;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Timestamp;
import io.cloudevents.CloudEventData;
import io.cloudevents.SpecVersion;
import io.cloudevents.core.CloudEventUtils;
Expand All @@ -27,7 +30,6 @@
import io.cloudevents.rw.CloudEventRWException;
import io.cloudevents.rw.CloudEventWriter;
import io.cloudevents.v1.proto.CloudEvent;
import io.cloudevents.v1.proto.CloudEvent.*;

import java.net.URI;
import java.time.Instant;
Expand Down Expand Up @@ -244,16 +246,20 @@ public CloudEvent end(CloudEventData data) throws CloudEventRWException {

// If it's a proto message we can handle that directly.
if (data instanceof ProtoCloudEventData) {

final ProtoCloudEventData protoData = (ProtoCloudEventData) data;
final Message m = protoData.getMessage();
if (m != null) {
// If it's already an 'Any' don't re-pack it.
if (m instanceof Any) {
protoBuilder.setProtoData((Any) m);
}else {
protoBuilder.setProtoData(Any.pack(m));
}
final Any anAny = protoData.getAny();

// Even though our local implementation cannot be instantiated
// with NULL data nothing stops somebody from having their own
// variant that isn't as 'safe'.

if (anAny != null) {
protoBuilder.setProtoData(anAny);
} else {
throw CloudEventRWException.newOther("ProtoCloudEventData: getAny() was NULL");
}

} else {
if (Objects.equals(dataContentType, PROTO_DATA_CONTENT_TYPE)) {
// This will throw if the data provided is not an Any. The protobuf CloudEvent spec requires proto data to be stored as
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ public void testBasic() {
ProtoDataWrapper pdw = new ProtoDataWrapper(quote1);

assertThat(pdw).isNotNull();
assertThat(pdw.getMessage()).isNotNull();
assertThat(pdw.getAny()).isNotNull();
assertThat(pdw.toBytes()).withFailMessage("toBytes was NULL").isNotNull();
assertThat(pdw.toBytes()).withFailMessage("toBytes[] returned empty array").hasSizeGreaterThan(0);

// This is current behavior and will probably change in the next version.
assertThat(pdw.getMessage()).isInstanceOf(io.cloudevents.test.v1.proto.Test.Quote.class);
// Ensure it's a Quote.
assertThat(pdw.getAny().is(io.cloudevents.test.v1.proto.Test.Quote.class)).isTrue();
}

@Test
Expand Down Expand Up @@ -91,7 +91,7 @@ public void testBytes() {
final byte[] actData = pdw.toBytes();

// Verify
Arrays.equals(expData, actData);
assertThat(Arrays.equals(actData, expData)).isTrue();

}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,16 @@

import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventData;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.format.EventFormat;
import io.cloudevents.test.v1.proto.Test.Decimal;
import io.cloudevents.test.v1.proto.Test.Quote;
import org.junit.jupiter.api.Test;

import java.math.BigDecimal;
import java.net.URI;
import org.junit.jupiter.api.Test;
import io.cloudevents.test.v1.proto.Test.Quote;
import io.cloudevents.test.v1.proto.Test.Decimal;

import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.assertj.core.api.Assertions.assertThat;
Expand All @@ -49,7 +49,7 @@ public void verifyDataWrapper() {
assertThat(ced).isInstanceOf(ProtoCloudEventData.class);

ProtoCloudEventData pced = (ProtoCloudEventData) ced;
assertThat(pced.getMessage()).isNotNull();
assertThat(pced.getAny()).isNotNull();
}

@Test
Expand Down Expand Up @@ -82,12 +82,11 @@ public void verifyMessage() throws InvalidProtocolBufferException {
assertThat(eventData).isNotNull();
assertThat(eventData).isInstanceOf(ProtoCloudEventData.class);

Message newMessage = ((ProtoCloudEventData) eventData).getMessage();
assertThat(newMessage).isNotNull();
assertThat(newMessage).isInstanceOf(Any.class);
Any newAny = ((ProtoCloudEventData) eventData).getAny();
assertThat(newAny).isNotNull();

// Hydrate the data - maybe there's a cleaner way to do this.
Quote newQuote = ((Any) newMessage).unpack(Quote.class);
Quote newQuote = newAny.unpack(Quote.class);
assertThat(newQuote).ignoringRepeatedFieldOrder().isEqualTo(pyplQuote);
}

Expand Down