Merge pull request #49 from fabiojose/master

Kafka binding, spec 0.3 support, preparing for the future
This commit is contained in:
Fabio José 2019-09-25 15:16:27 -03:00 committed by GitHub
commit e625f3a33a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 13999 additions and 1412 deletions

54
CHANGELOG.md Normal file
View File

@ -0,0 +1,54 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
## [Unreleased]
TODO
## [0.3.0]
### Added
- [Attributes](./api/src/main/java/io/cloudevents/Attributes.java) marker
interface for context attributes
- Support for [Spec v0.3](https://github.com/cloudevents/spec/tree/v0.3)
- [ExtensionFormat](./api/src/main/java/io/cloudevents/ExtensionFormat.java)
interface for extensions
- [HTTP Marshallers](./api/src/main/java/io/cloudevents/v02/http/Marshallers.java) with bare minium marshallers for HTTP Transport Binding
- [HTTP Unmarshallers](./api/src/main/java/io/cloudevents/v02/http/Unmarshallers.java) with bare minium unmarshallers for HTTP Transport Binding
- [Kafka Marshallers](./kafka/src/main/java/io/cloudevents/v02/kafka/Marshallers.java) with bare minimum marshallers for Kafka Transport Binding
- [CloudEventsKafkaProducer](./kafka/src/main/java//io/cloudevents/kafka/CloudEventsKafkaProducer.java) The CloudEvents producer that uses Kafka Clients API
- [Kafka Unmarshallers](./kafka/src/main/java/io/cloudevents/v02/kafka/Unmarshallers.java) with bare minium unmarshallers for Kafka Transport Binding
- [CloudEventsKafkaConsumer](./kafka/src/main/java//io/cloudevents/kafka/CloudEventsKafkaConsumer.java) The CloudEvents consumer that uses Kafka Clients API
- [BinaryMarshaller](./api/src/main/java/io/cloudevents/format/BinaryMarshaller.java) To help in the case of you need to create your own marshallers for binary content mode
- [BinaryUnmarshaller](./api/src/main/java/io/cloudevents/format/BinaryUnmarshaller.java) To help in the case of you need to create your own unmarshallers for binary content mode
- [StructuredMarshaller](./api/src/main/java/io/cloudevents/format/StructuredMarshaller.java) To help in the case of you need to create your own marshallers for structured content mode
- [StructuredUnmarshaller](./api/src/main/java/io/cloudevents/format/StructuredUnmarshaller.java) To help in the case of you need to create your own unmarshallers for structured content mode
### Changed
- CloudEvent interface signature, breaking the backward compatibility
- Class: `io.cloudevents.v02.CloudEvent` moved to `io.cloudevents.v02.CloudEventImpl`
- Class: `io.cloudevents.v02.CloudEventImpl` had changed its signature, breaking the
backward compatibility
- Method: `io.cloudevents.json.Json.fromInputStream` had moved to Generics signature
- Class: `io.cloudevents.http.V02HttpTransportMappers` moved to
`io.cloudevents.v02.http.BinaryAttributeMapperImpl` and had changed is signature breaking the backward compatibility
### Removed
- Support for Spec v0.1
- Class: `io.cloudevents.impl.DefaultCloudEventImpl`, in favor of a impl for each
version
- Class: `io.cloudevents.CloudEventBuilder`, in favor of a builder for each version
- Enum: `io.cloudevents.SpecVersion`, in favor of specialization of specs
- Method: `io.cloudevents.json.Json.decodeCloudEvent`
- Class: `io.cloudevents.http.V01HttpTransportMappers` due the unsupported v0.1
- interface: `io.cloudevents.http.HttpTransportAttributes`, in favor of the new
abstract envelope signature
- interface: `io.cloudevents.Extension` in favor of
`io.cloudevents.extensions.ExtensionFormat`
[Unreleased]: https://github.com/cloudevents/sdk-java/compare/v0.3.0...HEAD
[0.3.0]: https://github.com/cloudevents/sdk-java/compare/v0.2.1...v0.3.0

View File

@ -7,6 +7,8 @@
A Java API for the [CloudEvents specification](https://github.com/cloudevents/spec)
__Checkout the [changelog](./CHANGELOG.md)__
## Motivation
The [CloudEvents specification](https://github.com/cloudevents/spec) is a vendor-neutral specification for defining the format of event data that is being exchanged between different cloud systems. The specification basically defines an abstract envelope for any event data payload, without knowing specific implementation details of the actual underlying event. The current version of the spec is at `0.2` and it describes a simple event format, which was demonstrated at [KubeCon 2018](https://youtu.be/TZPPjAv12KU) using different _Serverless platforms_, such as [Apache Openwhisk](https://github.com/apache/incubator-openwhisk).
@ -19,7 +21,7 @@ For Maven based projects, use the following to configure the CloudEvents Java SD
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-api</artifactId>
<version>0.2.2</version>
<version>0.3.0</version>
</dependency>
```
@ -27,8 +29,8 @@ Application developers can now create strongly-typed CloudEvents, such as:
```java
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEvent;
import io.cloudevents.v02.ExtensionFormat;
import io.cloudevents.v02.CloudEventImpl;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.json.Json;
import io.cloudevents.extensions.DistributedTracingExtension;
@ -43,10 +45,11 @@ final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
dt.setTracestate("rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
final ExtensionFormat tracing = new DistributedTracingExtension.InMemory(dt);
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
// passing in the given attributes
final CloudEvent<MyCustomEvent> cloudEvent = new CloudEventBuilder<MyCustomEvent>()
final CloudEventImpl<MyCustomEvent> cloudEvent =
CloudEventBuilder.<MyCustomEvent>builder()
.withType(eventType)
.withId(eventId)
.withSource(src)
@ -58,6 +61,12 @@ final CloudEvent<MyCustomEvent> cloudEvent = new CloudEventBuilder<MyCustomEvent
final String json = Json.encode(cloudEvent);
```
There are [other detailed ways](./api/README.md) of how to use the marshallers and unmarshallers with HTTP transport binding.
## Kafka
The support for kafka transport binding is available. Read the [documentation and examples](./kafka/README.md) of use.
## Possible Integrations
The API is kept simple, for allowing a wide range of possible integrations:

546
api/README.md Normal file
View File

@ -0,0 +1,546 @@
# CloudEvents API
The base classes, interfaces and low-level APIs to use CloudEvents.
## How to Use
Here we will see how to use the pre-configure marshallers and unmarshallers.
### Binary Marshaller
The high-level API to marshal CloudEvents as binary content mode.
```java
import java.net.URI;
import java.time.ZonedDateTime;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.Wire;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
import io.cloudevents.v02.http.Marshallers;
//...
/*Create a tracing extension*/
final DistributedTracingExtension dt =
new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
/*Format it as extension format*/
final ExtensionFormat tracing =
new DistributedTracingExtension.Format(dt);
/* Build a CloudEvent instance */
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withType("com.github.pull.create")
.withSource(URI.create("https://github.com/cloudevents/spec/pull"))
.withId("A234-1234-1234")
.withSchemaurl(URI.create("http://my.br"))
.withTime(ZonedDateTime.now())
.withContenttype("text/plain")
.withData("my-data")
.withExtension(tracing)
.build();
/* Marshal the event as a Wire instance */
Wire<String, String, String> wire =
Marshallers.<String>
binary()
.withEvent(() -> ce)
.marshal();
/*
* Use the wire result, getting the headers map
* and the actual payload
*/
wire.getHeaders(); //Map<String, String>
wire.getPayload(); //Optional<String> which has the JSON
// Use in the transport binding: http, kafka, etc ...
```
### Binary Umarshaller
The high-level API to unmarshal CloudEvents from binary content mode.
```java
import java.util.HashMap;
import java.util.Map;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.v02.AttributesImpl;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.http.Unmarshallers;
// . . .
/* The HTTP headers example */
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("ce-specversion", "0.2");
httpHeaders.put("ce-type", "com.github.pull.create");
httpHeaders.put("ce-source", "https://github.com/cloudevents/spec/pull");
httpHeaders.put("ce-id", "A234-1234-1234");
httpHeaders.put("ce-time", "2018-04-05T17:31:00Z");
httpHeaders.put("ce-schemaurl", "http://my.br");
httpHeaders.put("my-ext", "my-custom extension");
httpHeaders.put("traceparent", "0");
httpHeaders.put("tracestate", "congo=4");
httpHeaders.put("Content-Type", "application/json");
/* The payload */
String myPayload = "{\"foo\" : \"rocks\", \"name\" : \"jocker\"}";
/* Unmarshals as CloudEvent instance */
CloudEvent<AttributesImpl, Map> event =
Unmarshallers.binary(Map.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> myPayload)
.unmarshal();
/* Use the CloudEvent instance attributes, data and extensions */
event.getAttributes();
event.getData();
event.getExtensions();
```
### Structured Marshaller
The high-level API to marshal CloudEvents as structured content mode.
```java
import java.net.URI;
import java.time.ZonedDateTime;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
import io.cloudevents.v02.http.Marshallers;
// . . .
final DistributedTracingExtension dt =
new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing =
new DistributedTracingExtension.Format(dt);
final CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withType("com.github.pull.create")
.withSource(URI.create("https://github.com/cloudevents/spec/pull"))
.withId("A234-1234-1234")
.withSchemaurl(URI.create("http://my.br"))
.withTime(ZonedDateTime.now())
.withContenttype("text/plain")
.withData("my-data")
.withExtension(tracing)
.build();
final Wire<String, String, String> wire =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
/*
* Use the wire result, getting the headers map
* and the actual payload
*/
wire.getHeaders(); //Map<String, String>
wire.getPayload(); //Optional<String> which has the JSON
// Use in the transport binding: http, kafka, etc ...
```
### Structured Unmarshaller
The high-level API to unmarshal CloudEvents as structured content mode.
```java
import java.util.HashMap;
import java.util.Map;
import io.cloudevents.CloudEvent;
import io.cloudevents.v02.AttributesImpl;
import io.cloudevents.v02.http.Unmarshallers;
// . . .
/* The HTTP Headers */
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
/* Distributed Tracing */
httpHeaders.put("traceparent", "0x200");
httpHeaders.put("tracestate", "congo=9");
/* JSON Payload */
String payload = "{\"data\":{\"wow\":\"yes!\"},\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"application/json\"}";
/* Unmarshalling . . . */
CloudEvent<AttributesImpl, Map> event =
Unmarshallers.structured(Map.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> payload)
.unmarshal();
/* Use the event instance */
event.getAttributes();
event.getExtensions();
event.getData();
```
## Low-level (Un)Marshalling
When you need to process different formats, instead of JSON, or if you have a custom extension implementation. You must develop your own (un)marshallers.
We provide a way to make it easy, well, or with less pain at all.
Let us introduce the step builders:
- [BinaryMarshaller](./src/main/java/io/cloudevents/format/BinaryMarshaller.java)
- [StructuredMarshaller](./src/main/java/io/cloudevents/format/StructuredMarshaller.java)
- [BinaryUnmarshaller](./src/main/java/io/cloudevents/format/BinaryUnmarshaller.java)
- [StructuredUnmarshaller](./src/main/java/io/cloudevents/format/StructuredUnmarshaller.java)
All of them follow the [step builder pattern](https://java-design-patterns.com/patterns/step-builder/), that helps a lot when you are new in the data (un)marshalling. To support them we have a [bunch of functional interfaces](./src/main/java/io/cloudevents/fun), used by each step.
### Marshaller
Well, this is how build marshaller using the low-level API.
**Binary Marshaller**
```java
/*
* The imports used by the example bellow
*/
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.BinaryMarshaller;
import io.cloudevents.format.Wire;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.json.Json;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.Accessor;
import io.cloudevents.v02.AttributesImpl;
// . . .
/*
* Step 0. Define the types - there are four
* - Type 1 -> AttributesImpl: the implementation of attributes
* - Type 2 -> Much..........: the type CloudEvents' 'data'
* - Type 3 -> String........: the type of payload that will result of marshalling
* - Type 4 -> String........: the type of headers values. String for HTTP, byte[] for Kafka . . .
*/
EventStep<AttributesImpl, Much, String, String> builder =
BinaryMarshaller.<AttributesImpl, Much, String, String>
builder()
/*
* Step 1. The attributes marshalling
* - in this step we must provide an impl able to marshal AttributesImpl into a
* Map<String, String>
*/
.map(AttributesImpl::marshal)
/*
* Step 2. Access the internal list of extensions
* - here we must provide an accessor for the internal list of extensions
*/
.map(Accessor::extensionsOf)
/*
* Step 3. The extensions marshalling
* - we must provide an impl able to marshal a Collection<ExtensionFormat> into a
* Map<String, String>
*/
.map(ExtensionFormat::marshal)
/*
* Step 4. Mapping to headers
* - provide an impl able to map from attributes and extensions into a
* Map<String, String>, the headers of transport binding.
*/
.map(HeaderMapper::map)
/*
* Step 5. The data marshaller
* - provider an impl able to marshal the CloudEvents' data into payload
* for transport
*/
.map(Json.<Much, String>marshaller()::marshal)
/*
* Step 6. The wire builder
* - to make easy to get the marshalled paylaod and the headers we
* have the Wire
* - here must to provide a way to create new instance of Wire
* - now we get the EventStep<AttributesImpl, Much, String, String>, a common step
* that every marshaller returns
* - from here we just call withEvent() and marshal() methods
*/
.builder(Wire<String, String, String>::new);
/*
* Using the marshaller
*/
Wire<String, String, String> wire =
builder
.withEvent(() -> myEvent)
.marshal();
}
```
**Structured Marshaller**
```java
/*
* The imports used by the example bellow
*/
import java.util.HashMap;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.StructuredMarshaller;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.json.Json;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.Accessor;
import io.cloudevents.v02.AttributesImpl;
// . . .
/*
* Step 0. Define the types - there are four
* - Type 1 -> AttributesImpl: the implementation of attributes
* - Type 2 -> Much..........: the type CloudEvents' 'data'
* - Type 3 -> String........: the type of payload that will result of marshalling
* - Type 4 -> String........: the type of headers values. String for HTTP, byte[] for Kafka . . .
*/
EventStep<AttributesImpl, Much, String, String> builder =
StructuredMarshaller.<AttributesImpl, Much, String, String>
builder()
/*
* Step 1. Setting the media type for the envelope
* - here we must to say the name of media type header and it's value
*/
.mime("Content-Type", "application/cloudevents+json")
/*
* Step 2. The marshaller for envelope
* - we must provide an impl able to marshal the cloudevents envelope
*/
.map((event) -> {
return Json.<CloudEvent<AttributesImpl, Much>, String>
marshaller().marshal(event, new HashMap<>());
})
/*
* Step 3. Access the internal list of extensions
* - here we must provide an accessor for the internal list of extensions
*/
.map(Accessor::extensionsOf)
/*
* Step 4. The extensions marshalling
* - we must provide an impl able to marshal a Collection<ExtensionFormat> into a Map<String, String>
*/
.map(ExtensionFormat::marshal)
/*
* Step 5. Mapping to headers
* - provide an impl able to map from attributes and extensions into a Map<String, String>, the headers of transport binding.
* - now we get the EventStep<AttributesImpl, Much, String, String>, a common step that every marshaller returns
* - from here we just call withEvent() and marshal() methods
*/
.map(HeaderMapper::map);
/*
* Using the marshaller
*/
Wire<String, String, String> wire =
builder
.withEvent(() -> myEvent)
.marshal();
```
### Unmarshaller
**Binary Unmarshaller**
```java
/*
* The imports used by the example bellow
*/
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.format.BinaryUnmarshaller;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.json.Json;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.AttributesImpl;
import io.cloudevents.v02.CloudEventBuilder;
// . . .
/*
* Step 0. Define the types - there are three
* - Type 1 -> AttributesImpl: the implementation of attributes
* - Type 2 -> Much..........: the type CloudEvents' 'data'
* - Type 3 -> String........: the type of payload used in the unmarshalling
*/
HeadersStep<AttributesImpl, Much, String> builder =
BinaryUnmarshaller.<AttributesImpl, Much, String>
builder()
/*
* Step 1. Mapping from headers Map to attributes one
* - we must provide a mapper able to map from transport headers to a map of attributes
* - this like a translation, removing the prefixes or unknow names for example
*/
.map(AttributeMapper::map)
/*
* Step 2. The attributes ummarshalling
* - we must provide an impl able to unmarshal a Map of attributes into
* an instance of Attributes
*/
.map(AttributesImpl::unmarshal)
/*
* Step 3. The data umarshaller
* - we may provive more than one unmarshaller per media type
* - we must provide an impl able to unmashal the payload into
* the actual 'data' type instance
*/
.map("application/json", Json.umarshaller(Much.class)::unmarshal)
/*
* Step 3'. Another data unmarshaller
*/
.map("media type", (payload, headers) -> {
return null;
})
/*
* When we are ok with data unmarshallers we call next()
*/
.next()
/*
* Step 4. The extension mapping
* - we must provider an impl able to map from transport headers to map of extensions
* - we may use this for extensions that lives in the transport headers
*/
.map(ExtensionMapper::map)
/*
* Step 5. The extension unmarshaller
* - we must provide an impl able to unmarshal from map of extenstions into
* actual ones
*/
.map(DistributedTracingExtension::unmarshall)
/*
* Step 5'. Another extension unmarshaller
*/
.map((extensionsMap) -> {
return null;
})
/*
* When we are ok with extensions unmarshallers we call next()
*/
.next()
/*
* Step 6. The CloudEvent builder
* - we must provide an impl able to take the extensions, data and attributes
* and build CloudEvent instances
* - now we get the HeadersStep<AttributesImpl, Much, String>, a common step
* that event unmarshaller must returns
* - from here we just call withHeaders(), withPayload() and unmarshal()
*/
.builder(CloudEventBuilder.<Much>builder()::build);
/*
* Using the unmarshaller
*/
CloudEvent<AttributesImpl, Much> myEvent =
builder
.withHeaders(() -> transportHeaders)
.withPayload(() -> payload)
.unmarshal();
```
**Structured Unmarshaller**
```java
/*
* Step 0. Define the types - there are three
* - Type 1 -> AttributesImpl: the implementation of attributes
* - Type 2 -> Much..........: the type CloudEvents' 'data'
* - Type 3 -> String........: the type of payload used in the unmarshalling
*/
HeadersStep<AttributesImpl, Much, String> step =
StructuredUnmarshaller.<AttributesImpl, Much, String>
builder()
/*
* Step 1. The extension mapping
* - we must provider an impl able to map from transport headers to map of extensions
* - we may use this for extensions that lives in the transport headers
*/
.map(ExtensionMapper::map)
/*
* Step 2. The extension unmarshaller
* - we must provide an impl able to unmarshal from map of extenstions into actual ones
*/
.map(DistributedTracingExtension::unmarshall)
/*
* Step 2'. When we are ok with extension unmarshallers, call next()
*/
.next()
/*
* Step 3. Envelope unmarshaller
* - we must provide an impl able to unmarshal the envelope into cloudevents
* - now we get the HeadersStep<AttributesImpl, Much, String>, a common step that event unmarshaller must returns
* - from here we just call withHeaders(), withPayload() and unmarshal()
*/
.map((payload, extensions) -> {
CloudEventImpl<Much> event =
Json.<CloudEventImpl<Much>>
decodeValue(payload, CloudEventImpl.class, Much.class);
CloudEventBuilder<Much> builder =
CloudEventBuilder.<Much>builder(event);
extensions.get().forEach(extension -> {
builder.withExtension(extension);
});
return builder.build();
});
/*
* Using the unmarshaller
*/
CloudEvent<AttributesImpl, Much> myEvent =
step
.withHeaders(() -> transportHeaders)
.withPayload(() -> payload)
.unmarshal();
```

View File

@ -20,13 +20,13 @@
<parent>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-parent</artifactId>
<version>0.2.2-SNAPSHOT</version>
<version>0.3.0-SNAPSHOT</version>
</parent>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-api</artifactId>
<name>CloudEvents - API</name>
<version>0.2.2-SNAPSHOT</version>
<version>0.3.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>

View File

@ -0,0 +1,37 @@
/**
* Copyright 2019 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;
import java.util.Optional;
import com.fasterxml.jackson.annotation.JsonIgnore;
/**
* The marker interface for CloudEvents attributes
*
* @author fabiojose
*
*/
public interface Attributes {
/**
* A common way to get the media type of CloudEvents 'data';
* @return If has a value, it MUST follows the <a href="https://tools.ietf.org/html/rfc2046">RFC2046</a>
*/
@JsonIgnore
Optional<String> getMediaType();
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2019 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;
/**
*
* @author fabiojose
*
*/
public interface Builder<A extends Attributes, T> {
/**
* To build a brand new instance of {@link CloudEvent}
*/
CloudEvent<A, T> build();
/**
* To build a brand new instance of {@link CloudEvent} with another
* type of 'data'
* @param <TT> The new type of 'data'
* @param id The new id for the new instance
* @param base The base {@link CloudEvent} to copy its attributes
* @param newData The new 'data'
*/
<TT> CloudEvent<A, TT> build(CloudEvent<A, T> base, String id, TT newData);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright 2018 The CloudEvents Authors
* Copyright 2019 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.
@ -15,64 +15,32 @@
*/
package io.cloudevents;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.impl.DefaultCloudEventImpl;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* An abstract event envelope, representing the 0.2 version of the <a href="https://github.com/cloudevents/spec/blob/master/spec.md">CNCF CloudEvent spec</a>.
* An abstract event envelope
*
* @author fabiojose
*
* @param <A> The attributes type
* @param <T> The 'data' type
*/
@JsonDeserialize(as = DefaultCloudEventImpl.class)
public interface CloudEvent<T> {
public interface CloudEvent<A extends Attributes, T> {
/**
* Type of occurrence which has happened. Often this property is used for routing, observability, policy enforcement, etc.
*/
String getType();
/**
* The version of the CloudEvents specification which the event uses. This enables the interpretation of the context.
*/
String getSpecVersion();
/**
* This describes the event producer. Often this will include information such as the type of the event source, the organization publishing the event, and some unique identifiers.
* The exact syntax and semantics behind the data encoded in the URI is event producer defined.
*/
URI getSource();
/**
* ID of the event. The semantics of this string are explicitly undefined to ease the implementation of producers. Enables deduplication.
*/
String getId();
/**
* Timestamp of when the event happened.
*/
Optional<ZonedDateTime> getTime();
/**
* A link to the schema that the data attribute adheres to.
*/
Optional<URI> getSchemaURL();
/**
* Describe the data encoding format
*/
Optional<String> getContentType();
/**
* The event payload. The payload depends on the eventType, schemaURL and eventTypeVersion, the payload is encoded into a media format which is specified by the contentType attribute (e.g. application/json).
*/
Optional<T> getData();
/**
*
*/
Optional<List<Extension>> getExtensions();
}
/**
* The event context attributes
*/
A getAttributes();
/**
* The event data
*/
Optional<T> getData();
/**
* The event extensions
*/
Map<String, Object> getExtensions();
}

View File

@ -1,130 +0,0 @@
/**
* Copyright 2018 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;
import io.cloudevents.impl.DefaultCloudEventImpl;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Builder class to create a Java Object representing a CloudEvent implementation
* @param <T> type of the data field
*/
public class CloudEventBuilder<T> {
private String specversion;
private String contentType;
private String type;
private URI source;
private String id;
private ZonedDateTime time;
private URI schemaURL;
private T data;
private final List<Extension> extensions = new ArrayList<>();
/**
* The version of the CloudEvents specification which the event uses.
*/
public CloudEventBuilder<T> specVersion(final String specVersion) {
this.specversion = specVersion;
return this;
}
/**
* Type of occurrence which has happened. Often this property is used for routing, observability, policy enforcement, etc.
*/
public CloudEventBuilder<T> type(final String type) {
this.type = type;
return this;
}
/**
* This describes the event producer. Often this will include information such as the type of the event source, the organization publishing the event, and some unique identifiers.
* The exact syntax and semantics behind the data encoded in the URI is event producer defined.
*/
public CloudEventBuilder<T> source(final URI source) {
this.source = source;
return this;
}
/**
* ID of the event. The semantics of this string are explicitly undefined to ease the implementation of producers. Enables deduplication.
*/
public CloudEventBuilder<T> id(final String id) {
this.id = id;
return this;
}
/**
* Timestamp of when the event happened.
*/
public CloudEventBuilder<T> time(final ZonedDateTime time) {
this.time = time;
return this;
}
/**
* A link to the schema that the data attribute adheres to.
*/
public CloudEventBuilder<T> schemaURL(final URI schemaURL) {
this.schemaURL = schemaURL;
return this;
}
/**
* Describe the data encoding format
*/
public CloudEventBuilder<T> contentType(final String contentType) {
this.contentType = contentType;
return this;
}
/**
* The event payload. The payload depends on the type and schemaURL, the payload is encoded into a media format which is specified by the contenttype attribute (e.g. application/json).
*/
public CloudEventBuilder<T> data(final T data) {
this.data = data;
return this;
}
public CloudEventBuilder<T> extension(final Extension extension) {
this.extensions.add(extension);
return this;
}
/**
* Constructs a new {@link CloudEvent} with the previously-set configuration.
*/
public CloudEvent<T> build() {
// forcing latest (default) version
if (specversion == null) {
specversion = SpecVersion.DEFAULT.toString();
}
if (type == null || source == null || id == null) {
throw new IllegalArgumentException("please provide all required fields");
}
return new DefaultCloudEventImpl<T>(type, specversion, source, id, time, schemaURL, contentType, data, extensions);
}
}

View File

@ -1,68 +0,0 @@
/**
* Copyright 2018 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;
import java.util.HashMap;
import java.util.Map;
public enum SpecVersion {
V_01("0.1"),
V_02("0.2"),
DEFAULT(V_02.toString());
private final String version;
SpecVersion(final String version) {
this.version = version;
}
@Override
public String toString() {
return version;
}
public String version() {
return version;
}
public static SpecVersion fromVersion(final String version) {
if (version == null)
return null;
final SpecVersion specVersion= VERSION_TO_SPEC.get(version);
if (specVersion == null)
throw new IllegalArgumentException();
return specVersion;
}
private static final Map<String, SpecVersion> VERSION_TO_SPEC =
new HashMap<>();
static
{
SpecVersion[] instances = SpecVersion.class.getEnumConstants();
for (int i = 0; i < instances.length; i++)
{
VERSION_TO_SPEC.put(instances[i].toString(), instances[i]);
}
}
}

View File

@ -1,9 +1,12 @@
package io.cloudevents.extensions;
import io.cloudevents.Extension;
import io.cloudevents.v02.ExtensionFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
public class DistributedTracingExtension implements Extension {
public class DistributedTracingExtension {
private String traceparent;
private String tracestate;
@ -65,8 +68,6 @@ public class DistributedTracingExtension implements Extension {
return true;
}
/**
* The in-memory format for distributed tracing.
* <br/>
@ -74,24 +75,62 @@ public class DistributedTracingExtension implements Extension {
* @author fabiojose
*
*/
public static class InMemory implements ExtensionFormat {
public static class Format implements ExtensionFormat {
public static final String IN_MEMORY_KEY = "distributedTracing";
private final Extension extension;
public InMemory(DistributedTracingExtension extension) {
this.extension = extension;
public static final String TRACE_PARENT_KEY = "traceparent";
public static final String TRACE_STATE_KEY = "tracestate";
private final InMemoryFormat memory;
private final Map<String, String> transport = new HashMap<>();
public Format(DistributedTracingExtension extension) {
Objects.requireNonNull(extension);
memory = InMemoryFormat.of(IN_MEMORY_KEY, extension,
DistributedTracingExtension.class);
transport.put(TRACE_PARENT_KEY, extension.getTraceparent());
transport.put(TRACE_STATE_KEY, extension.getTracestate());
}
@Override
public String getKey() {
return IN_MEMORY_KEY;
}
@Override
public Extension getExtension() {
return extension;
}
@Override
public InMemoryFormat memory() {
return memory;
}
@Override
public Map<String, String> transport() {
return transport;
}
}
/**
* Unmarshals the {@link DistributedTracingExtension} based on map of extensions.
* @param exts
* @return
*/
public static Optional<ExtensionFormat> unmarshall(
Map<String, String> exts) {
String traceparent = exts.get(Format.TRACE_PARENT_KEY);
String tracestate = exts.get(Format.TRACE_STATE_KEY);
if(null!= traceparent && null!= tracestate) {
DistributedTracingExtension dte = new DistributedTracingExtension();
dte.setTraceparent(traceparent);
dte.setTracestate(tracestate);
InMemoryFormat inMemory =
InMemoryFormat.of(Format.IN_MEMORY_KEY, dte,
DistributedTracingExtension.class);
return Optional.of(
ExtensionFormat.of(inMemory,
new SimpleEntry<>(Format.TRACE_PARENT_KEY, traceparent),
new SimpleEntry<>(Format.TRACE_STATE_KEY, tracestate))
);
}
return Optional.empty();
}
}

View File

@ -0,0 +1,101 @@
/**
* Copyright 2019 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.extensions;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* Defines a way to add custom extension in the abstract envelop.
*
* @author fabiojose
*
*/
public interface ExtensionFormat {
/**
* The in-memory format to be used by the structured content mode.
*/
InMemoryFormat memory();
/**
* The transport format to be used by the binary content mode.
*/
Map<String, String> transport();
public static ExtensionFormat of(final InMemoryFormat inMemory,
final String key, final String value) {
final Map<String, String> transport = new HashMap<>();
transport.put(key, value);
return new ExtensionFormat() {
@Override
public InMemoryFormat memory() {
return inMemory;
}
@Override
public Map<String, String> transport() {
return Collections.unmodifiableMap(transport);
}
};
}
@SafeVarargs
public static ExtensionFormat of(final InMemoryFormat inMemory,
Entry<String, String> ... transport){
Objects.requireNonNull(inMemory);
Objects.requireNonNull(transport);
final Map<String, String> transports = Arrays.asList(transport)
.stream()
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
return new ExtensionFormat() {
@Override
public InMemoryFormat memory() {
return inMemory;
}
@Override
public Map<String, String> transport() {
return transports;
}
};
}
/**
* Marshals a collection of {@link ExtensionFormat} to {@code Map<String, String>}
* @param extensions
* @return
*/
public static Map<String, String> marshal(Collection<ExtensionFormat>
extensions) {
return extensions.stream()
.map(ExtensionFormat::transport)
.flatMap(t -> t.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue));
}
}

View File

@ -0,0 +1,63 @@
/**
* Copyright 2019 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.extensions;
/**
* The in-memory format to be used by the structured content mode.
* <br>
* See details about in-memory format
* <a href="https://github.com/cloudevents/spec/blob/v0.2/documented-extensions.md#usage">here</a>
* @author fabiojose
*
*/
public interface InMemoryFormat {
/**
* The in-memory format key
*/
String getKey();
/**
* The in-memory format value
*/
Object getValue();
/**
* The type reference for the value. That should be used during the
* unmarshal.
*/
Class<?> getValueType();
public static InMemoryFormat of(final String key, final Object value,
final Class<?> valueType) {
return new InMemoryFormat() {
@Override
public String getKey() {
return key;
}
@Override
public Object getValue() {
return value;
}
@Override
public Class<?> getValueType() {
return valueType;
}
};
}
}

View File

@ -0,0 +1,199 @@
/**
* Copyright 2019 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.format;
import java.util.Collection;
import java.util.Map;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.format.builder.MarshalStep;
import io.cloudevents.fun.AttributeMarshaller;
import io.cloudevents.fun.DataMarshaller;
import io.cloudevents.fun.ExtensionFormatAccessor;
import io.cloudevents.fun.ExtensionMarshaller;
import io.cloudevents.fun.FormatHeaderMapper;
import io.cloudevents.fun.WireBuilder;
/**
*
* @author fabiojose
*
*/
public final class BinaryMarshaller {
private BinaryMarshaller() {}
/**
* Gets a new builder instance
* @param <A> The attributes type
* @param <T> The 'data' type
* @param <P> The payload type
* @param <H> The type of headers value
* @return
*/
public static <A extends Attributes, T, P, H>
AttributeMarshalStep<A, T, P, H> builder() {
return new Builder<A, T, P, H>();
}
public static interface AttributeMarshalStep<A extends Attributes, T, P, H> {
/**
* Marshals the {@link Attributes} instance into a
* {@code Map<String, String>}
* @param marshaller
* @return
*/
ExtensionsAccessorStep<A, T, P, H> map(AttributeMarshaller<A> marshaller);
}
public static interface ExtensionsAccessorStep<A extends Attributes, T, P, H> {
/**
* To get access of internal collection of {@link ExtensionFormat}
* @param accessor
* @return
*/
ExtensionsStep<A, T, P, H> map(ExtensionFormatAccessor<A, T> accessor);
}
public static interface ExtensionsStep<A extends Attributes, T, P, H> {
/**
* Marshals the collection of {@link ExtensionFormat} into a
* {@code Map<String, String>}
* @param marshaller
* @return
*/
HeaderMapStep<A, T, P, H> map(ExtensionMarshaller marshaller);
}
public static interface HeaderMapStep<A extends Attributes, T, P, H> {
/**
* Marshals the map of attributes and extensions into a map of headers
* @param mapper
* @return
*/
DataMarshallerStep<A, T, P, H> map(FormatHeaderMapper<H> mapper);
}
public static interface DataMarshallerStep<A extends Attributes, T, P, H> {
/**
* Marshals the 'data' into payload
* @param marshaller
* @return
*/
BuilderStep<A, T, P, H> map(DataMarshaller<P, T, H> marshaller);
}
public static interface BuilderStep<A extends Attributes, T, P, H> {
/**
* Builds the {@link Wire} to use for wire transfer
* @param builder
* @return
*/
EventStep<A, T, P, H> builder(WireBuilder<P, String, H> builder);
}
private static final class Builder<A extends Attributes, T, P, H> implements
AttributeMarshalStep<A, T, P, H>,
ExtensionsAccessorStep<A, T, P, H>,
ExtensionsStep<A, T, P, H>,
DataMarshallerStep<A, T, P, H>,
HeaderMapStep<A, T, P, H>,
BuilderStep<A, T, P, H>,
EventStep<A, T, P, H>,
MarshalStep<P, H> {
private AttributeMarshaller<A> attributeMarshaller;
private ExtensionFormatAccessor<A, T> extensionsAccessor;
private ExtensionMarshaller extensionMarshaller;
private FormatHeaderMapper<H> headerMapper;
private DataMarshaller<P, T, H> dataMarshaller;
private WireBuilder<P, String, H> wireBuilder;
private Supplier<CloudEvent<A, T>> eventSupplier;
@Override
public ExtensionsAccessorStep<A, T, P, H> map(AttributeMarshaller<A> marshaller) {
this.attributeMarshaller = marshaller;
return this;
}
@Override
public ExtensionsStep<A, T, P, H> map(ExtensionFormatAccessor<A, T> accessor) {
this.extensionsAccessor = accessor;
return this;
}
@Override
public HeaderMapStep<A, T, P, H> map(ExtensionMarshaller marshaller) {
this.extensionMarshaller = marshaller;
return this;
}
@Override
public DataMarshallerStep<A, T, P, H> map(FormatHeaderMapper<H> mapper) {
this.headerMapper = mapper;
return this;
}
@Override
public BuilderStep<A, T, P, H> map(DataMarshaller<P, T, H> marshaller) {
this.dataMarshaller = marshaller;
return this;
}
@Override
public EventStep<A, T, P, H> builder(WireBuilder<P, String, H> builder) {
this.wireBuilder = builder;
return this;
}
@Override
public MarshalStep<P, H> withEvent(Supplier<CloudEvent<A, T>> event) {
this.eventSupplier = event;
return this;
}
@Override
public Wire<P, String, H> marshal() {
CloudEvent<A, T> event = eventSupplier.get();
Map<String, String> attributesMap =
attributeMarshaller.marshal(event.getAttributes());
Collection<ExtensionFormat> extensionsFormat =
extensionsAccessor.extensionsOf(event);
Map<String, String> extensionsMap =
extensionMarshaller.marshal(extensionsFormat);
Map<String, H> headers =
headerMapper.map(attributesMap, extensionsMap);
P payload = null;
if(event.getData().isPresent()) {
payload = dataMarshaller.marshal(event.getData().get(),
headers);
}
return wireBuilder.build(payload, headers);
}
}
}

View File

@ -0,0 +1,244 @@
/**
* Copyright 2019 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.format;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.format.builder.PayloadStep;
import io.cloudevents.format.builder.UnmarshalStep;
import io.cloudevents.fun.AttributeUnmarshaller;
import io.cloudevents.fun.BinaryFormatAttributeMapper;
import io.cloudevents.fun.DataUnmarshaller;
import io.cloudevents.fun.EventBuilder;
import io.cloudevents.fun.ExtensionUmarshaller;
import io.cloudevents.fun.FormatExtensionMapper;
/**
*
* @author fabiojose
*
*/
public final class BinaryUnmarshaller {
private BinaryUnmarshaller() {}
/**
* Gets a new builder instance
* @param <A> The attributes type
* @param <T> The 'data' type
* @param <P> The payload type
* @return
*/
public static <A extends Attributes, T, P> AttributeMapStep<A, T, P>
builder() {
return new Builder<A, T, P>();
}
public interface AttributeMapStep<A extends Attributes, T, P> {
/**
* Maps the map of headers into map of attributes
* @param unmarshaller
* @return
*/
AttributeUmarshallStep<A, T, P> map(BinaryFormatAttributeMapper unmarshaller);
}
public interface AttributeUmarshallStep<A extends Attributes, T, P> {
/**
* Unmarshals the map of attributes into instance of {@link Attributes}
* @param unmarshaller
* @return
*/
DataUnmarshallerStep<A, T, P> map(AttributeUnmarshaller<A> unmarshaller);
}
public interface DataUnmarshallerStep<A extends Attributes, T, P> {
/**
* Unmarshals the payload into actual 'data' type
* @param unmarshaller
* @return
*/
DataUnmarshallerStep<A, T, P> map(String mime, DataUnmarshaller<P, T, A> unmarshaller);
ExtensionsMapStep<A, T, P> next();
}
public interface ExtensionsMapStep<A extends Attributes, T, P> {
/**
* Maps the headers map into map of extensions
* @param mapper
* @return
*/
ExtensionsStep<A, T, P> map(FormatExtensionMapper mapper);
}
public interface ExtensionsStepBegin<A extends Attributes, T, P> {
/**
* Starts the configuration for extensions unmarshal
* @return
*/
ExtensionsStep<A, T, P> beginExtensions();
}
public interface ExtensionsStep<A extends Attributes, T, P> {
/**
* Unmarshals a extension, based on the map of extensions.
*
* <br>
* <br>
* This is an optional step, because you do not have extensions or
* do not want to process them at all.
*
* @param unmarshaller
* @return
*/
ExtensionsStep<A, T, P> map(ExtensionUmarshaller unmarshaller);
/**
* Ends the configuration for extensions unmarshal
* @return
*/
BuilderStep<A, T, P> next();
}
public interface BuilderStep<A extends Attributes, T, P> {
/**
* Takes the builder to build {@link CloudEvent} instances
* @param builder
* @return
*/
HeadersStep<A, T, P> builder(EventBuilder<T, A> builder);
}
private static final class Builder<A extends Attributes, T, P> implements
AttributeMapStep<A, T, P>,
AttributeUmarshallStep<A, T, P>,
DataUnmarshallerStep<A, T, P>,
ExtensionsMapStep<A, T, P>,
ExtensionsStep<A, T, P>,
BuilderStep<A, T, P>,
HeadersStep<A, T, P>,
PayloadStep<A, T, P>,
UnmarshalStep<A, T>{
private BinaryFormatAttributeMapper attributeMapper;
private AttributeUnmarshaller<A> attributeUnmarshaller;
private Map<String, DataUnmarshaller<P, T, A>> dataUnmarshallers =
new HashMap<>();
private FormatExtensionMapper extensionMapper;
private Set<ExtensionUmarshaller> extensionUnmarshallers =
new HashSet<>();
private EventBuilder<T, A> eventBuilder;
private Supplier<Map<String, Object>> headersSupplier;
private Supplier<P> payloadSupplier;
@Override
public AttributeUmarshallStep<A, T, P> map(BinaryFormatAttributeMapper mapper) {
this.attributeMapper = mapper;
return this;
}
@Override
public DataUnmarshallerStep<A, T, P> map(AttributeUnmarshaller<A> unmarshaller) {
this.attributeUnmarshaller = unmarshaller;
return this;
}
@Override
public DataUnmarshallerStep<A, T, P> map(String mime, DataUnmarshaller<P, T, A> unmarshaller) {
this.dataUnmarshallers.put(mime, unmarshaller);
return this;
}
public Builder<A, T, P> next() {
return this;
}
@Override
public ExtensionsStep<A, T, P> map(FormatExtensionMapper mapper) {
this.extensionMapper = mapper;
return this;
}
@Override
public ExtensionsStep<A, T, P> map(ExtensionUmarshaller unmarshaller) {
this.extensionUnmarshallers.add(unmarshaller);
return this;
}
@Override
public HeadersStep<A, T, P> builder(EventBuilder<T, A> builder) {
this.eventBuilder = builder;
return this;
}
@Override
public PayloadStep<A, T, P> withHeaders(
Supplier<Map<String, Object>> headers) {
this.headersSupplier = headers;
return this;
}
@Override
public UnmarshalStep<A, T> withPayload(Supplier<P> payload) {
this.payloadSupplier = payload;
return this;
}
@Override
public CloudEvent<A, T> unmarshal() {
Map<String, Object> headers = headersSupplier.get();
P payload = payloadSupplier.get();
Map<String, String> attributesMap = attributeMapper.map(headers);
A attributes = attributeUnmarshaller.unmarshal(attributesMap);
T data = attributes.getMediaType()
.map((mime) -> {
return dataUnmarshallers.get(mime);
})
.filter((un) -> null != un)
.map(unmarshaller ->
unmarshaller.unmarshal(payload, attributes))
.orElse(null);
final Map<String, String> extensionsMap =
extensionMapper.map(headers);
List<ExtensionFormat> extensions =
extensionUnmarshallers.stream()
.map(unmarshaller ->
unmarshaller.unmarshal(extensionsMap))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
return eventBuilder.build(data, attributes, extensions);
}
}
}

View File

@ -0,0 +1,185 @@
/**
* Copyright 2019 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.format;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.format.builder.MarshalStep;
import io.cloudevents.fun.EnvelopeMarshaller;
import io.cloudevents.fun.ExtensionFormatAccessor;
import io.cloudevents.fun.ExtensionMarshaller;
import io.cloudevents.fun.FormatHeaderMapper;
/**
*
* @author fabiojose
*
*/
public class StructuredMarshaller {
StructuredMarshaller() {}
/**
*
* @param <A> The attributes type
* @param <T> The CloudEvents 'data' type
* @param <P> The CloudEvents marshaled envelope type
* @param <H> The header type
* @return A new builder to build structured mashaller
*/
public static <A extends Attributes, T, P, H> MediaTypeStep<A, T, P, H>
builder() {
return new Builder<>();
}
public static interface MediaTypeStep<A extends Attributes, T, P, H> {
/**
* Sets the media type of CloudEvents envelope
* @param headerName Example {@code Content-Type} for HTTP
* @param mediaType Example: {@code application/cloudevents+json}
*/
EnvelopeMarshallerStep<A, T, P, H> mime(String headerName, H mediaType);
}
public static interface EnvelopeMarshallerStep<A extends Attributes, T, P, H> {
/**
* Sets the marshaller for the CloudEvent
* @param marshaller
*/
ExtensionAccessorStep<A, T, P, H> map(EnvelopeMarshaller<A, T, P> marshaller);
}
public static interface ExtensionAccessorStep<A extends Attributes, T, P, H> {
/**
* To skip the extension special handling
*/
EventStep<A, T, P, H> skip();
ExtensionMarshallerStep<A, T, P, H> map(ExtensionFormatAccessor<A, T> accessor);
}
public static interface ExtensionMarshallerStep<A extends Attributes, T, P, H> {
HeaderMapperStep<A, T, P, H> map(ExtensionMarshaller marshaller);
}
public static interface HeaderMapperStep<A extends Attributes, T, P, H> {
EventStep<A, T, P, H> map(FormatHeaderMapper<H> mapper);
}
private static final class Builder<A extends Attributes, T, P, H> implements
MediaTypeStep<A, T, P, H>,
EnvelopeMarshallerStep<A, T, P, H>,
ExtensionAccessorStep<A, T, P, H>,
ExtensionMarshallerStep<A, T, P, H>,
HeaderMapperStep<A, T, P, H>,
EventStep<A, T, P, H>,
MarshalStep<P, H>{
private static final Map<String, String> NO_ATTRS =
new HashMap<>();
private String headerName;
private H mediaType;
private EnvelopeMarshaller<A, T, P> marshaller;
private ExtensionFormatAccessor<A, T> extensionAccessor;
private ExtensionMarshaller extensionMarshaller;
private FormatHeaderMapper<H> headerMapper;
private Supplier<CloudEvent<A, T>> event;
@Override
public EnvelopeMarshallerStep<A, T, P, H> mime(String headerName, H mediaType) {
Objects.requireNonNull(headerName);
Objects.requireNonNull(mediaType);
this.headerName = headerName;
this.mediaType = mediaType;
return this;
}
@Override
public ExtensionAccessorStep<A, T, P, H> map(EnvelopeMarshaller<A, T, P> marshaller) {
Objects.requireNonNull(marshaller);
this.marshaller = marshaller;
return this;
}
@Override
public EventStep<A, T, P, H> skip() {
return this;
}
@Override
public ExtensionMarshallerStep<A, T, P, H> map(ExtensionFormatAccessor<A, T> accessor) {
Objects.requireNonNull(accessor);
this.extensionAccessor = accessor;
return this;
}
@Override
public HeaderMapperStep<A, T, P, H> map(ExtensionMarshaller marshaller) {
Objects.requireNonNull(marshaller);
this.extensionMarshaller = marshaller;
return this;
}
@Override
public EventStep<A, T, P, H> map(FormatHeaderMapper<H> mapper) {
Objects.requireNonNull(mapper);
this.headerMapper = mapper;
return this;
}
@Override
public MarshalStep<P, H> withEvent(Supplier<CloudEvent<A, T>> event) {
Objects.requireNonNull(event);
this.event = event;
return this;
}
@Override
public Wire<P, String, H> marshal() {
CloudEvent<A, T> ce = event.get();
P payload = marshaller.marshal(ce);
Map<String, H> headers =
Optional.ofNullable(extensionAccessor)
.map(accessor -> accessor.extensionsOf(ce))
.map(extensions -> extensionMarshaller.marshal(extensions))
.map(extensions -> headerMapper.map(NO_ATTRS, extensions))
.orElse(new HashMap<>());
headers.put(headerName, mediaType);
return new Wire<>(payload, headers);
}
}
}

View File

@ -0,0 +1,162 @@
/**
* Copyright 2019 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.format;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.format.builder.PayloadStep;
import io.cloudevents.format.builder.UnmarshalStep;
import io.cloudevents.fun.EnvelopeUnmarshaller;
import io.cloudevents.fun.ExtensionUmarshaller;
import io.cloudevents.fun.FormatExtensionMapper;
/**
*
* @author fabiojose
*
*/
public class StructuredUnmarshaller {
StructuredUnmarshaller() {}
public static <A extends Attributes, T, P> ExtensionMapperStep<A, T, P>
builder() {
return new Builder<>();
}
public static interface ExtensionMapperStep<A extends Attributes, T, P> {
EnvelopeUnmarshallerStep<A, T, P> skip();
ExtensionUnmarshallerStep<A, T, P> map(FormatExtensionMapper mapper);
}
public static interface ExtensionUnmarshallerStep<A extends Attributes, T, P> {
ExtensionUnmarshallerStep<A, T, P> map(ExtensionUmarshaller unmarshaller);
EnvelopeUnmarshallerStep<A, T, P> next();
}
public static interface EnvelopeUnmarshallerStep<A extends Attributes, T, P> {
HeadersStep<A, T, P> map(EnvelopeUnmarshaller<A, T, P> unmarshaller);
}
private static final class Builder<A extends Attributes, T, P> implements
ExtensionMapperStep<A, T, P>,
ExtensionUnmarshallerStep<A, T, P>,
EnvelopeUnmarshallerStep<A, T, P>,
HeadersStep<A, T, P>,
PayloadStep<A, T, P>,
UnmarshalStep<A, T>{
private FormatExtensionMapper extensionMapper;
private Set<ExtensionUmarshaller> extensionUnmarshallers =
new HashSet<>();
private EnvelopeUnmarshaller<A, T, P> unmarshaller;
private Supplier<Map<String, Object>> headersSupplier;
private Supplier<P> payloadSupplier;
@Override
public Builder<A, T, P> next() {
return this;
}
@Override
public EnvelopeUnmarshallerStep<A, T, P> skip() {
return this;
}
@Override
public ExtensionUnmarshallerStep<A, T, P> map(FormatExtensionMapper mapper) {
Objects.requireNonNull(mapper);
this.extensionMapper = mapper;
return this;
}
@Override
public ExtensionUnmarshallerStep<A, T, P> map(ExtensionUmarshaller unmarshaller) {
Objects.requireNonNull(unmarshaller);
this.extensionUnmarshallers.add(unmarshaller);
return this;
}
@Override
public HeadersStep<A, T, P> map(EnvelopeUnmarshaller<A, T, P> unmarshaller) {
Objects.requireNonNull(unmarshaller);
this.unmarshaller = unmarshaller;
return this;
}
@Override
public PayloadStep<A, T, P> withHeaders(Supplier<Map<String, Object>> headers) {
Objects.requireNonNull(headers);
this.headersSupplier = headers;
return this;
}
@Override
public UnmarshalStep<A, T> withPayload(Supplier<P> payload) {
Objects.requireNonNull(payload);
this.payloadSupplier = payload;
return this;
}
@Override
public CloudEvent<A, T> unmarshal() {
Map<String, Object> headers = headersSupplier.get();
P payload = payloadSupplier.get();
final Map<String, String> extensionsMap =
Optional.ofNullable(extensionMapper)
.map(mapper -> mapper.map(headers))
.orElse(new HashMap<>());
CloudEvent<A, T> result =
unmarshaller.unmarshal(payload,
() ->
extensionUnmarshallers.stream()
.map(unmarshaller ->
unmarshaller.unmarshal(extensionsMap))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList()));
return result;
}
}
}

View File

@ -0,0 +1,55 @@
/**
* Copyright 2019 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.format;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a result of binary marshal, to be used by the wire transfer
*
* @author fabiojose
* @param <T> The payload type
* @param <K> The header key type
* @param <V> The header value type
*/
public class Wire<T, K, V> {
private final T payload;
private final Map<K, V> headers;
public Wire(T payload, Map<K, V> headers) {
Objects.requireNonNull(headers);
this.payload = payload;
this.headers = headers;
}
/**
* The payload
*/
public Optional<T> getPayload() {
return Optional.ofNullable(payload);
}
/**
* The headers
*/
public Map<K, V> getHeaders() {
return Collections.unmodifiableMap(headers);
}
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2019 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.format.builder;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
/**
*
* @author fabiojose
*
* @param <A> The attributes type
* @param <T> The 'data' type
* @param <P> The payload type
* @param <H> The headers value type
*/
public interface EventStep<A extends Attributes, T, P, H> {
/**
* Supplies the {@link CloudEvent} instance which will be marshaled
* @param event cloud event to marshal
* @return The next step of builder
*/
MarshalStep<P, H> withEvent(Supplier<CloudEvent<A, T>> event);
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2019 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.format.builder;
import java.util.Map;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
/**
*
* @author fabiojose
*
* @param <A> The attributes type
* @param <T> The 'data' type
* @param <P> The payload type
*/
public interface HeadersStep<A extends Attributes, T, P> {
/**
* Supplies a map of headers to be used by the unmarshaller
* @param headers
* @return The next step of builder
*/
PayloadStep<A, T, P> withHeaders(Supplier<Map<String, Object>> headers);
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2019 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.format.builder;
import io.cloudevents.CloudEvent;
import io.cloudevents.format.Wire;
/**
*
* @author fabiojose
*
* @param <P> The payload type
* @param <H> The headers value type
*/
public interface MarshalStep<P, H> {
/**
* Marshals the {@link CloudEvent} instance as {@link Wire}
* @return The wire to use in the transports bindings
*/
Wire<P, String, H> marshal();
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2019 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.format.builder;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
/**
*
* @author fabiojose
*
* @param <A> The attributes type
* @param <T> The 'data' type
* @param <P> The payload type
*/
public interface PayloadStep<A extends Attributes, T, P> {
/**
* Supplies the payload that will be used by the unmarshaller
* @param payload
* @return The next step o builder
*/
UnmarshalStep<A, T> withPayload(Supplier<P> payload);
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2019 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.format.builder;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
/**
*
* @author fabiojose
*
* @param <A> The attributes type
* @param <T> The 'data' type
*/
public interface UnmarshalStep<A extends Attributes, T> {
/**
* Unmarshals the payload and headers to {@link CloudEvent} instance
* @return New cloud event instance
*/
CloudEvent<A, T> unmarshal();
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
import io.cloudevents.Attributes;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface AttributeMarshaller<A extends Attributes> {
/**
* Marshals the {@link Attributes} to map of attributes.
*/
Map<String, String> marshal(A attributes);
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
import io.cloudevents.Attributes;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface AttributeUnmarshaller<A extends Attributes> {
/**
* Unmarshals the map of CloudEvent attributes into actual ones
*/
A unmarshal(Map<String, String> attributes);
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface BinaryFormatAttributeMapper {
/**
* Maps the 'headers' of binary format into CloudEvent attributes
* @param headers
* @return
*/
Map<String, String> map(Map<String, Object> headers);
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
/**
*
* @author fabiojose
*
* @param <P> The payload type
* @param <T> The 'data' type
* @param <H> The type of headers value
*/
@FunctionalInterface
public interface DataMarshaller<P, T, H> {
/**
* Marshals the 'data' into payload
* @param data
* @param headers
* @return
* @throws RuntimeException When something bad happens during the marshal process
*/
P marshal(T data, Map<String, H> headers) throws RuntimeException;
}

View File

@ -0,0 +1,40 @@
/**
* Copyright 2019 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.fun;
import io.cloudevents.Attributes;
/**
* For the 'data' unmarshalling.
*
* @author fabiojose
*
* @param <P> The payload type
* @param <T> The 'data' type
*/
@FunctionalInterface
public interface DataUnmarshaller<P, T, A extends Attributes> {
/**
* Unmarshals the payload into 'data'
* @param payload
* @param attributes
* @return
* @throws RuntimeException If something bad happens during the umarshal
*/
T unmarshal(P payload, A attributes);
}

View File

@ -0,0 +1,35 @@
/**
* Copyright 2019 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.fun;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface EnvelopeMarshaller<A extends Attributes, T, P> {
/**
* Marshals a CloudEvent instance into the wire format
* @param event The CloudEvents instance
* @return the wire format
*/
P marshal(CloudEvent<A, T> event);
}

View File

@ -0,0 +1,42 @@
/**
* Copyright 2019 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.fun;
import java.util.List;
import java.util.function.Supplier;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
public interface EnvelopeUnmarshaller<A extends Attributes, T, P> {
/**
* Unmarshals the payload into {@link CloudEvent} instance implementation
*
* @param payload The envelope payload
* @param extensions Supplies a list of {@link ExtensionFormat}
* @return The unmarshalled impl of CloudEvent
*/
CloudEvent<A, T> unmarshal(P payload,
Supplier<List<ExtensionFormat>> extensions);
}

View File

@ -0,0 +1,39 @@
/**
* Copyright 2019 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.fun;
import java.util.Collection;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
/**
* To build the event.
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface EventBuilder<T, A extends Attributes> {
/**
* Builds a new event using 'data', 'attributes' and 'extensions'
*/
CloudEvent<A, T> build(T data, A attributes,
Collection<ExtensionFormat> extensions);
}

View File

@ -0,0 +1,25 @@
package io.cloudevents.fun;
import java.util.Collection;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface ExtensionFormatAccessor<A extends Attributes, T> {
/**
* To get access to the internal collection of {@link ExtensionFormat} inside
* the {@link CloudEvent} implementation
*
* @param cloudEvent
* @return
*/
Collection<ExtensionFormat> extensionsOf(CloudEvent<A, T> cloudEvent);
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2019 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.fun;
import java.util.Collection;
import java.util.Map;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface ExtensionMarshaller {
/**
* Marshals a collections of {@link ExtensionFormat} into map
* @param extensions
* @return
*/
Map<String, String> marshal(Collection<ExtensionFormat> extensions);
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
import java.util.Optional;
import io.cloudevents.extensions.ExtensionFormat;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface ExtensionUmarshaller {
/**
* Unmarshals the map of extensions into {@link ExtensionFormat}
* @param extensions
* @return
*/
Optional<ExtensionFormat> unmarshal(Map<String, String> extensions);
}

View File

@ -1,5 +1,5 @@
/**
* Copyright 2018 The CloudEvents Authors
* Copyright 2019 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.
@ -13,7 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudevents;
package io.cloudevents.fun;
public interface Extension {
import java.util.Map;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface FormatExtensionMapper {
/**
* Maps the 'headers' of binary format into extensions
* @param headers
*/
Map<String, String> map(Map<String, Object> headers);
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
/**
*
* @author fabiojose
* @param <H> The header value type
*
*/
@FunctionalInterface
public interface FormatHeaderMapper<H> {
/**
* Maps the 'attributes' and 'extensions' of CloudEvent envelop to
* 'headers' of binary format
* @param attributes
* @return
*/
Map<String, H> map(Map<String, String> attributes,
Map<String, String> extensions);
}

View File

@ -0,0 +1,38 @@
/**
* Copyright 2019 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.fun;
import java.util.Map;
import io.cloudevents.format.Wire;
/**
*
* @author fabiojose
*
*/
@FunctionalInterface
public interface WireBuilder<P, K, V> {
/**
* Builds a wire format
* @param payload
* @param headers
* @return
*/
Wire<P, K, V> build(P payload, Map<K, V> headers);
}

View File

@ -1,46 +0,0 @@
/**
* Copyright 2018 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.http;
import io.cloudevents.SpecVersion;
import static io.cloudevents.SpecVersion.V_01;
public interface HttpTransportAttributes {
// required attrs
String typeKey();
String specVersionKey();
String sourceKey();
String idKey();
// none-required attrs
String timeKey();
String schemaUrlKey();
static HttpTransportAttributes getHttpAttributesForSpec(final SpecVersion specVersion) {
switch (specVersion) {
case V_01: return new V01HttpTransportMappers();
case V_02:
case DEFAULT: return new V02HttpTransportMappers();
}
// you should not be here!
throw new IllegalArgumentException("Could not find proper version");
}
}

View File

@ -1,51 +0,0 @@
/**
* Copyright 2018 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.http;
public class V01HttpTransportMappers implements HttpTransportAttributes {
public static final String SPEC_VERSION_KEY = "ce-cloudEventsVersion";
@Override
public String typeKey() {
return "ce-eventType";
}
@Override
public String specVersionKey() {
return SPEC_VERSION_KEY;
}
@Override
public String sourceKey() {
return "ce-source";
}
@Override
public String idKey() {
return "ce-eventID";
}
@Override
public String timeKey() {
return "ce-eventTime";
}
@Override
public String schemaUrlKey() {
return "ce-schemaURL";
}
}

View File

@ -1,47 +0,0 @@
/**
* Copyright 2018 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.http;
public class V02HttpTransportMappers extends V01HttpTransportMappers {
public static final String SPEC_VERSION_KEY = "ce-specversion";
@Override
public String typeKey() {
return "ce-type";
}
@Override
public String specVersionKey() {
return SPEC_VERSION_KEY;
}
@Override
public String idKey() {
return "ce-id";
}
@Override
public String timeKey() {
return "ce-time";
}
@Override
public String schemaUrlKey() {
return "ce-schemaurl";
}
}

View File

@ -1,165 +0,0 @@
/**
* Copyright 2018 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.impl;
import com.fasterxml.jackson.annotation.JsonAlias;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.CloudEvent;
import io.cloudevents.Extension;
import io.cloudevents.json.ZonedDateTimeDeserializer;
import java.io.Serializable;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
/**
* Default Java implementation of the CloudEvent API.
*
* @param <T> generic type of the underlying data field.
*/
@JsonIgnoreProperties(value = { "eventTypeVersion", "extensions" }) // was removed from 0.1
public class DefaultCloudEventImpl<T> implements CloudEvent<T>, Serializable {
private static final long serialVersionUID = 2L;
private String specversion;
private String type = null;
private URI source = null;
private String id = null;
private ZonedDateTime time = null;
private URI schemaURL = null;
private String contentType = null;
private T data = null;
private List<Extension> extensions = null;
public DefaultCloudEventImpl(final String type, final String specversion, final URI source, final String id, final ZonedDateTime time, final URI schemaURL, final String contentType, final T data, final List<Extension> extensions) {
this.specversion = specversion;
this.type = type;
this.source = source;
this.id = id;
this.time = time;
this.schemaURL = schemaURL;
this.contentType = contentType;
this.data = data;
this.extensions = extensions;
}
DefaultCloudEventImpl() {
// no-op
}
@Override
public String getSpecVersion() {
return specversion;
}
@Override
public String getType() {
return type;
}
@Override
public URI getSource() {
return source;
}
@Override
public String getId() {
return id;
}
@Override
public Optional<ZonedDateTime> getTime() {
return Optional.ofNullable(time);
}
@Override
public Optional<URI> getSchemaURL() {
return Optional.ofNullable(schemaURL);
}
@Override
public Optional<String> getContentType() {
return Optional.ofNullable(contentType);
}
@Override
public Optional<T> getData() {
return Optional.ofNullable(data);
}
@Override
public Optional<List<Extension>> getExtensions() {
return Optional.ofNullable(extensions);
}
// protected setters, used for (JSON) deserialization
@JsonAlias({"specversion", "specVersion", "cloudEventsVersion"})
void setSpecversion(String specversion) {
this.specversion = specversion;
}
@JsonAlias({"type", "eventType"})
void setType(String type) {
this.type = type;
}
void setSource(URI source) {
this.source = source;
}
@JsonAlias({"id", "eventID"})
void setId(String id) {
this.id = id;
}
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
@JsonAlias({"time", "eventTime"})
void setTime(ZonedDateTime time) {
this.time = time;
}
void setSchemaURL(URI schemaURL) {
this.schemaURL = schemaURL;
}
@JsonAlias("contenttype")
void setContentType(String contentType) {
this.contentType = contentType;
}
void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "DefaultCloudEventImpl{" +
"specversion='" + specversion + '\'' +
", type='" + type + '\'' +
", source=" + source +
", id='" + id + '\'' +
", time=" + time +
", schemaURL=" + schemaURL +
", contentType='" + contentType + '\'' +
", data=" + data +
'}';
}
}

View File

@ -15,15 +15,20 @@
*/
package io.cloudevents.json;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.cloudevents.CloudEvent;
import io.cloudevents.impl.DefaultCloudEventImpl;
import java.io.InputStream;
import java.time.ZonedDateTime;
import java.util.Map;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
import io.cloudevents.Attributes;
import io.cloudevents.fun.DataMarshaller;
import io.cloudevents.fun.DataUnmarshaller;
public final class Json {
@ -54,11 +59,19 @@ public final class Json {
throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage());
}
}
public static CloudEvent fromInputStream(final InputStream inputStream) {
/**
* Encode a POJO to JSON using the underlying Jackson mapper.
*
* @param obj a POJO
* @return a byte array containing the JSON representation of the given POJO.
* @throws IllegalStateException if a property cannot be encoded.
*/
public static byte[] binaryEncode(final Object obj) throws IllegalStateException {
try {
return MAPPER.readValue(inputStream, DefaultCloudEventImpl.class);
return MAPPER.writeValueAsBytes(obj);
} catch (Exception e) {
e.printStackTrace();
throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage());
}
}
@ -73,32 +86,37 @@ public final class Json {
}
}
/**
* Decode a given JSON string to a CloudEvent .
*
* @param str the JSON string.
* @return an instance of CloudEvent
* @throws IllegalStateException when there is a parsing or invalid mapping.
*/
public static DefaultCloudEventImpl decodeCloudEvent(final String str) throws IllegalStateException {
return decodeValue(str, DefaultCloudEventImpl.class);
}
/**
* Decode a given JSON string to a POJO of the given class type.
*
* @param str the JSON string.
* @param clazz the class to map to.
* @param <T> the generic type.
* @return an instance of T
* @return an instance of T or {@code null} when {@code str} is an empty string or {@code null}
* @throws IllegalStateException when there is a parsing or invalid mapping.
*/
protected static <T> T decodeValue(final String str, final Class<T> clazz) throws IllegalStateException {
try {
return MAPPER.readValue(str, clazz);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage());
}
if(null!= str && !"".equals(str.trim())) {
try {
return MAPPER.readValue(str.trim(), clazz);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage());
}
}
return null;
}
protected static <T> T binaryDecodeValue(byte[] payload, final Class<T> clazz) {
if(null!= payload) {
try {
return MAPPER.readValue(payload, clazz);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage());
}
}
return null;
}
/**
@ -107,15 +125,148 @@ public final class Json {
* @param str the JSON string.
* @param type the type to map to.
* @param <T> the generic type.
* @return an instance of T
* @return an instance of T or {@code null} when {@code str} is an empty string or {@code null}
* @throws IllegalStateException when there is a parsing or invalid mapping.
*/
public static <T> T decodeValue(final String str, final TypeReference<T> type) throws IllegalStateException {
try {
return MAPPER.readValue(str, type);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage(), e);
}
if(null!= str && !"".equals(str.trim())) {
try {
return MAPPER.readValue(str.trim(), type);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage(), e);
}
}
return null;
}
/**
* Example of use:
* <pre>
* String someJson = "...";
* Class<?> clazz = Much.class;
*
* Json.decodeValue(someJson, CloudEventImpl.class, clazz);
* </pre>
* @param str The JSON String to parse
* @param parametrized Actual full type
* @param parameterClasses Type parameters to apply
* @param <T> the generic type.
* @return An instance of T or {@code null} when {@code str} is an empty string or {@code null}
* @see ObjectMapper#getTypeFactory
* @see TypeFactory#constructParametricType(Class, Class...)
*/
public static <T> T decodeValue(final String str, Class<?> parametrized,
Class<?>...parameterClasses) {
if(null!= str && !"".equals(str.trim())) {
try {
JavaType type =
MAPPER.getTypeFactory()
.constructParametricType(parametrized,
parameterClasses);
return MAPPER.readValue(str.trim(), type);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage(), e);
}
}
return null;
}
/**
* Example of use:
* <pre>
* String someJson = "...";
* Class<?> clazz = Much.class;
*
* Json.decodeValue(someJson, CloudEventImpl.class, clazz);
* </pre>
* @param json The JSON byte array to parse
* @param parametrized Actual full type
* @param parameterClasses Type parameters to apply
* @param <T> the generic type.
* @return An instance of T or {@code null} when {@code str} is an empty string or {@code null}
* @see ObjectMapper#getTypeFactory
* @see TypeFactory#constructParametricType(Class, Class...)
*/
public static <T> T binaryDecodeValue(final byte[] json, Class<?> parametrized,
Class<?>...parameterClasses) {
if(null!= json) {
try {
JavaType type =
MAPPER.getTypeFactory()
.constructParametricType(parametrized,
parameterClasses);
return MAPPER.readValue(json, type);
} catch (Exception e) {
throw new IllegalStateException("Failed to decode: " + e.getMessage(), e);
}
}
return null;
}
/**
* Creates a JSON Data Unmarshaller
* @param <T> The 'data' type
* @param <A> The attributes type
* @param type The type of 'data'
* @return A new instance of {@link DataUnmarshaller}
*/
public static <T, A extends Attributes> DataUnmarshaller<String, T, A>
umarshaller(Class<T> type) {
return new DataUnmarshaller<String, T, A>() {
@Override
public T unmarshal(String payload, A attributes) {
return Json.decodeValue(payload, type);
}
};
}
/**
* Unmarshals a byte array into T type
* @param <T> The 'data' type
* @param <A> The attributes type
* @param payload The byte array
* @param attribues
* @return The data objects
*/
public static <T, A extends Attributes> DataUnmarshaller<byte[], T, A>
binaryUmarshaller(Class<T> type) {
return new DataUnmarshaller<byte[], T, A>() {
@Override
public T unmarshal(byte[] payload, A attributes) {
return Json.binaryDecodeValue(payload, type);
}
};
}
/**
* Creates a JSON Data Marshaller that produces a {@link String}
* @param <T> The 'data' type
* @param <H> The type of headers value
* @return A new instance of {@link DataMarshaller}
*/
public static <T, H> DataMarshaller<String, T, H> marshaller() {
return new DataMarshaller<String, T, H>() {
@Override
public String marshal(T data, Map<String, H> headers) {
return Json.encode(data);
}
};
}
/**
* Marshalls the 'data' value as JSON, producing a byte array
* @param <T> The 'data' type
* @param <H> The type of headers value
* @param data The 'data' value
* @param headers The headers
* @return A byte array with 'data' value encoded JSON
*/
public static <T, H> byte[] binaryMarshal(T data,
Map<String, H> headers) {
return Json.binaryEncode(data);
}
private Json() {

View File

@ -25,8 +25,9 @@ import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
public class ZonedDateTimeDeserializer extends StdDeserializer<ZonedDateTime> {
private static final long serialVersionUID = 1L;
public ZonedDateTimeDeserializer() {
public ZonedDateTimeDeserializer() {
this(null);
}

View File

@ -0,0 +1,59 @@
/**
* Copyright 2019 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.v02;
import java.util.Collection;
import java.util.Objects;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.fun.ExtensionFormatAccessor;
/**
*
* @author fabiojose
*
*/
public final class Accessor {
private Accessor() {}
/**
* To get access the set of {@link ExtensionFormat} inside the
* event.
*
* <br>
* <br>
* This method follow the signature of
* {@link ExtensionFormatAccessor#extensionsOf(CloudEvent)}
*
* @param cloudEvent
* @throws IllegalArgumentException When argument is not an instance of {@link CloudEventImpl}
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <A extends Attributes, T> Collection<ExtensionFormat>
extensionsOf(CloudEvent<A, T> cloudEvent) {
Objects.requireNonNull(cloudEvent);
if(cloudEvent instanceof CloudEventImpl) {
CloudEventImpl impl = (CloudEventImpl)cloudEvent;
return impl.getExtensionsFormats();
}
throw new IllegalArgumentException("Invalid instance type: "
+ cloudEvent.getClass());
}
}

View File

@ -0,0 +1,220 @@
/**
* Copyright 2019 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.v02;
import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
import java.net.URI;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.Attributes;
import io.cloudevents.json.ZonedDateTimeDeserializer;
/**
*
* @author fabiojose
* @version 0.2
*/
@JsonInclude(value = Include.NON_ABSENT)
public class AttributesImpl implements Attributes {
@NotBlank
private final String type;
@NotBlank
@Pattern(regexp = "0\\.2")
private final String specversion;
@NotNull
private final URI source;
@NotBlank
private final String id;
private final ZonedDateTime time;
private final URI schemaurl;
private final String contenttype;
AttributesImpl(String type, String specversion, URI source,
String id, ZonedDateTime time, URI schemaurl, String contenttype) {
this.type = type;
this.specversion = specversion;
this.source = source;
this.id = id;
this.time = time;
this.schemaurl = schemaurl;
this.contenttype = contenttype;
}
/**
* Type of occurrence which has happened. Often this property is used for
* routing, observability, policy enforcement, etc.
*/
public String getType() {
return type;
}
/**
* ID of the event. The semantics of this string are explicitly
* undefined to ease the implementation of producers. Enables
* deduplication.
*/
public String getId() {
return id;
}
/**
* The version of the CloudEvents specification which the event uses.
* This enables the interpretation of the context.
*/
public String getSpecversion() {
return specversion;
}
/**
* This describes the event producer. Often this will include
* information such as the type of the event source, the organization
* publishing the event, and some unique identifiers.
* The exact syntax and semantics behind the data encoded in the URI
* is event producer defined.
*/
public URI getSource() {
return source;
}
/**
* Timestamp of when the event happened.
*/
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
public Optional<ZonedDateTime> getTime() {
return Optional.ofNullable(time);
}
/**
* A link to the schema that the data attribute adheres to.
*/
public Optional<URI> getSchemaurl() {
return Optional.ofNullable(schemaurl);
}
/**
* Describe the data encoding format
*/
public Optional<String> getContenttype() {
return Optional.ofNullable(contenttype);
}
/**
* {@inheritDoc}
*/
public Optional<String> getMediaType() {
return getContenttype();
}
@JsonCreator
public static AttributesImpl build(
@JsonProperty("id") String id,
@JsonProperty("source") URI source,
@JsonProperty("specversion") String specversion,
@JsonProperty("type") String type,
@JsonProperty("time") ZonedDateTime time,
@JsonProperty("schemaurl") URI schemaurl,
@JsonProperty("contenttype") String contenttype) {
return new AttributesImpl(type, specversion, source, id, time,
schemaurl, contenttype);
}
/**
* The attribute unmarshaller for the binary format, that receives a
* {@code Map} with attributes names as String and values as String.
*/
public static AttributesImpl unmarshal(Map<String, String> attributes) {
String type = attributes.get(ContextAttributes.type.name());
ZonedDateTime time =
Optional.ofNullable(attributes.get(ContextAttributes.time.name()))
.map((t) -> ZonedDateTime.parse(t,
ISO_ZONED_DATE_TIME))
.orElse(null);
String specversion = attributes.get(ContextAttributes.specversion.name());
URI source = URI.create(attributes.get(ContextAttributes.source.name()));
URI schemaurl =
Optional.ofNullable(attributes.get(ContextAttributes.schemaurl.name()))
.map(schema -> URI.create(schema))
.orElse(null);
String id = attributes.get(ContextAttributes.id.name());
String contenttype =
attributes.get(ContextAttributes.contenttype.name());
return AttributesImpl.build(id, source, specversion, type,
time, schemaurl, contenttype);
}
/**
* Creates the marshaller instance to marshall {@link AttributesImpl} as
* a {@link Map} of strings
*/
public static Map<String, String> marshal(AttributesImpl attributes) {
Objects.requireNonNull(attributes);
Map<String, String> result = new HashMap<>();
result.put(ContextAttributes.type.name(),
attributes.getType());
result.put(ContextAttributes.specversion.name(),
attributes.getSpecversion());
result.put(ContextAttributes.source.name(),
attributes.getSource().toString());
result.put(ContextAttributes.id.name(),
attributes.getId());
attributes.getTime().ifPresent((value) -> {
result.put(ContextAttributes.time.name(),
value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
});
attributes.getSchemaurl().ifPresent((schema) -> {
result.put(ContextAttributes.schemaurl.name(),
schema.toString());
});
attributes.getContenttype().ifPresent((ct) -> {
result.put(ContextAttributes.contenttype.name(), ct);
});
return result;
}
}

View File

@ -1,136 +0,0 @@
package io.cloudevents.v02;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.json.ZonedDateTimeDeserializer;
/**
*
* @author fabiojose
*
* Implemented using immutable data structure.
*
*/
@JsonInclude(value = Include.NON_ABSENT)
public class CloudEvent<T> {
@NotBlank
private final String type;
@NotBlank
@Pattern(regexp = "0\\.2")
private final String specversion;
@NotNull
private final URI source;
@NotBlank
private final String id;
private final ZonedDateTime time;
private final URI schemaurl;
private final String contenttype;
private final T data;
private final Map<String, Object> extensions;
CloudEvent(String id, URI source, String specversion, String type,
ZonedDateTime time, URI schemaurl, String contenttype,
T data, Set<ExtensionFormat> extensions) {
this.id = id;
this.source = source;
this.specversion = specversion;
this.type = type;
this.time = time;
this.schemaurl = schemaurl;
this.contenttype = contenttype;
this.data = data;
this.extensions = extensions
.stream()
.collect(Collectors
.toMap(ExtensionFormat::getKey,
ExtensionFormat::getExtension));
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getSpecversion() {
return specversion;
}
public URI getSource() {
return source;
}
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
public Optional<ZonedDateTime> getTime() {
return Optional.ofNullable(time);
}
public Optional<URI> getSchemaurl() {
return Optional.ofNullable(schemaurl);
}
public Optional<String> getContenttype() {
return Optional.ofNullable(contenttype);
}
public Optional<T> getData() {
return Optional.ofNullable(data);
}
@JsonAnyGetter
Map<String, Object> getExtensions() {
return Collections.unmodifiableMap(extensions);
}
@JsonAnySetter
void addExtension(String name, Object value) {
extensions.put(name, value);
}
@JsonCreator
public static <T> CloudEvent<T> build(
@JsonProperty("id") String id,
@JsonProperty("source") URI source,
@JsonProperty("specversion") String specversion,
@JsonProperty("type") String type,
@JsonProperty("time") ZonedDateTime time,
@JsonProperty("schemaurl") URI schemaurl,
@JsonProperty("contenttype") String contenttype,
@JsonProperty("data") T data) {
return new CloudEventBuilder<T>()
.withId(id)
.withSource(source)
.withType(type)
.withTime(time)
.withSchemaurl(schemaurl)
.withContenttype(contenttype)
.withData(data)
.build();
}
}

View File

@ -1,10 +1,27 @@
/**
* Copyright 2019 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.v02;
import static java.lang.String.format;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
@ -13,13 +30,22 @@ import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import io.cloudevents.Builder;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.fun.EventBuilder;
/**
* CloudEvent instances builder
*
* @author fabiojose
*
* @version 0.2
*/
public class CloudEventBuilder<T> {
public class CloudEventBuilder<T> implements EventBuilder<T, AttributesImpl>,
Builder<AttributesImpl, T> {
private CloudEventBuilder() {}
private static Validator VALIDATOR;
private static final String SPEC_VERSION = "0.2";
private static final String MESSAGE_SEPARATOR = ", ";
@ -37,23 +63,125 @@ public class CloudEventBuilder<T> {
private final Set<ExtensionFormat> extensions = new HashSet<>();
private Validator getValidator() {
return Validation.buildDefaultValidatorFactory().getValidator();
private static Validator getValidator() {
if(null== VALIDATOR) {
VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
}
return VALIDATOR;
}
/**
* Gets a brand new builder instance
* @param <T> The 'data' type
*/
public static <T> CloudEventBuilder<T> builder() {
return new CloudEventBuilder<T>();
}
/**
*
* @return An new {@link CloudEvent} immutable instance
* @param <T> The 'data' type
* @param base A base event to copy {@link CloudEvent#getAttributes()},
* {@link CloudEvent#getData()} and {@link CloudEvent#getExtensions()}
* @return
*/
public static <T> CloudEventBuilder<T> builder(
final CloudEvent<AttributesImpl, T> base) {
Objects.requireNonNull(base);
CloudEventBuilder<T> result = new CloudEventBuilder<>();
AttributesImpl attributes = base.getAttributes();
result
.withId(attributes.getId())
.withSource(attributes.getSource())
.withType(attributes.getType());
attributes.getTime().ifPresent((time) -> {
result.withTime(time);
});
attributes.getSchemaurl().ifPresent((schema) -> {
result.withSchemaurl(schema);
});
attributes.getContenttype().ifPresent(contenttype -> {
result.withContenttype(contenttype);
});
Accessor.extensionsOf(base)
.forEach(extension -> {
result.withExtension(extension);
});
base.getData().ifPresent(data -> {
result.withData(data);
});
return result;
}
/**
*
* @param <T> the type of 'data'
* @param data the value of data
* @param attributes the context attributes
* @return An new {@link CloudEventImpl} immutable instance
* @throws IllegalStateException When there are specification constraints
* violations
*/
public CloudEvent<T> build() {
CloudEvent<T> event = new CloudEvent<>(id, source, SPEC_VERSION, type,
time, schemaurl, contenttype, data, extensions);
public static <T> CloudEventImpl<T> of(T data, AttributesImpl attributes,
Collection<ExtensionFormat> extensions) {
CloudEventBuilder<T> builder = new CloudEventBuilder<T>()
.withId(attributes.getId())
.withSource(attributes.getSource())
.withType(attributes.getType());
Set<ConstraintViolation<CloudEvent<T>>> violations =
attributes.getTime().ifPresent((time) -> {
builder.withTime(time);
});
attributes.getSchemaurl().ifPresent((schema) -> {
builder.withSchemaurl(schema);
});
attributes.getContenttype().ifPresent(contenttype -> {
builder.withContenttype(contenttype);
});
extensions.stream()
.forEach(extension -> {
builder.withExtension(extension);
});
return builder.withData(data).build();
}
@Override
public CloudEvent<AttributesImpl, T> build(T data, AttributesImpl attributes,
Collection<ExtensionFormat> extensions){
return CloudEventBuilder.<T>of(data, attributes, extensions);
}
/**
* {@inheritDoc}
* @return An new {@link CloudEventImpl} immutable instance
* @throws IllegalStateException When there are specification constraints
* violations
*/
@Override
public CloudEventImpl<T> build() {
AttributesImpl attributes = new AttributesImpl(type, SPEC_VERSION,
source, id, time, schemaurl, contenttype);
CloudEventImpl<T> event = new CloudEventImpl<>(attributes, data, extensions);
Set<ConstraintViolation<Object>> violations =
getValidator().validate(event);
violations.addAll(getValidator().validate(event.getAttributes()));
final String errs =
violations.stream()
.map(v -> format(MESSAGE, v.getPropertyPath(), v.getMessage()))
@ -108,4 +236,41 @@ public class CloudEventBuilder<T> {
this.extensions.add(extension);
return this;
}
/**
* {@inheritDoc}
*/
@Override
public <TT> CloudEvent<AttributesImpl, TT>
build(CloudEvent<AttributesImpl, T> base, String id, TT newData) {
Objects.requireNonNull(base);
AttributesImpl attributes = base.getAttributes();
CloudEventBuilder<TT> builder = new CloudEventBuilder<TT>()
.withId(id)
.withSource(attributes.getSource())
.withType(attributes.getType());
attributes.getTime().ifPresent((time) -> {
builder.withTime(time);
});
attributes.getSchemaurl().ifPresent((schema) -> {
builder.withSchemaurl(schema);
});
attributes.getContenttype().ifPresent(contenttype -> {
builder.withContenttype(contenttype);
});
Collection<ExtensionFormat> extensions = Accessor.extensionsOf(base);
extensions.stream()
.forEach(extension -> {
builder.withExtension(extension);
});
return builder.withData(newData).build();
}
}

View File

@ -0,0 +1,137 @@
/**
* Copyright 2019 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.v02;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.extensions.InMemoryFormat;
/**
* The event implementation
*
* @author fabiojose
*
*/
@JsonInclude(value = Include.NON_ABSENT)
public class CloudEventImpl<T> implements CloudEvent<AttributesImpl, T> {
@JsonIgnore
@NotNull
private final AttributesImpl attributes;
private final T data;
@NotNull
private final Map<String, Object> extensions;
private final Set<ExtensionFormat> extensionsFormats;
CloudEventImpl(AttributesImpl attributes, T data,
Set<ExtensionFormat> extensions) {
this.attributes = attributes;
this.data = data;
this.extensions = extensions.stream()
.map(ExtensionFormat::memory)
.collect(Collectors.toMap(InMemoryFormat::getKey,
InMemoryFormat::getValue));
this.extensionsFormats = extensions;
}
/**
* Used by the {@link Accessor} to access the set of {@link ExtensionFormat}
*/
Set<ExtensionFormat> getExtensionsFormats() {
return extensionsFormats;
}
@JsonUnwrapped
@Override
public AttributesImpl getAttributes() {
return this.attributes;
}
/**
* The event payload. The payload depends on the eventType,
* schemaURL and eventTypeVersion, the payload is encoded into
* a media format which is specified by the contentType attribute
* (e.g. application/json).
*/
public Optional<T> getData() {
return Optional.ofNullable(data);
}
@JsonAnyGetter
public Map<String, Object> getExtensions() {
return Collections.unmodifiableMap(extensions);
}
/**
* The unique method that allows mutation. Used by
* Jackson Framework to inject the extensions.
*
* @param name Extension name
* @param value Extension value
*/
@JsonAnySetter
void addExtension(String name, Object value) {
extensions.put(name, value);
}
@JsonCreator
public static <T> CloudEventImpl<T> build(
@JsonProperty("id") String id,
@JsonProperty("source") URI source,
@JsonProperty("specversion") String specversion,
@JsonProperty("type") String type,
@JsonProperty("time") ZonedDateTime time,
@JsonProperty("schemaurl") URI schemaurl,
@JsonProperty("contenttype") String contenttype,
@JsonProperty("data") T data) {
return CloudEventBuilder.<T>builder()
.withId(id)
.withSource(source)
.withType(type)
.withTime(time)
.withSchemaurl(schemaurl)
.withContenttype(contenttype)
.withData(data)
.build();
}
}

View File

@ -0,0 +1,43 @@
/**
* Copyright 2019 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.v02;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* The specification reserved words: the context attributes
*
* @author fabiojose
*
*/
public enum ContextAttributes {
id,
source,
specversion,
type,
time,
schemaurl,
contenttype;
public static final List<String> VALUES =
Arrays.asList(ContextAttributes.values())
.stream()
.map(Enum::name)
.collect(Collectors.toList());
}

View File

@ -1,22 +0,0 @@
package io.cloudevents.v02;
import io.cloudevents.Extension;
/**
* See details about in-memory format
* <a href="https://github.com/cloudevents/spec/blob/v0.2/documented-extensions.md#usage">here</a>
*
* @author fabiojose
*
*/
public interface ExtensionFormat {
/**
* The in-memory format key
*/
String getKey();
/**
* The extension implementation
*/
Extension getExtension();
}

View File

@ -0,0 +1,82 @@
/**
* Copyright 2019 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.v02.http;
import static java.util.stream.Collectors.toMap;
import java.util.AbstractMap.SimpleEntry;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import io.cloudevents.fun.BinaryFormatAttributeMapper;
import io.cloudevents.v02.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.2
*/
public class AttributeMapper {
private AttributeMapper() {}
static final String HEADER_PREFIX = "ce-";
/**
* Following the signature of {@link BinaryFormatAttributeMapper#map(Map)}
* @param headers Map of HTTP request
* @return Map with spec attributes and values without parsing
* @see ContextAttributes
*/
public static Map<String, String> map(final Map<String, Object> headers) {
Objects.requireNonNull(headers);
final AtomicReference<Optional<Entry<String, Object>>> ct =
new AtomicReference<>();
ct.set(Optional.empty());
Map<String, String> result = headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.peek(header -> {
if("content-type".equals(header.getKey())) {
ct.set(Optional.ofNullable(header));
}
})
.filter(header -> header.getKey().startsWith(HEADER_PREFIX))
.map(header -> new SimpleEntry<>(header.getKey()
.substring(HEADER_PREFIX.length()), header.getValue()))
.map(header -> new SimpleEntry<>(header.getKey(),
header.getValue().toString()))
.collect(toMap(Entry::getKey, Entry::getValue));
ct.get().ifPresent(contentType -> {
result.put(ContextAttributes.contenttype.name(),
contentType.getValue().toString());
});
return result;
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright 2019 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.v02.http;
import java.util.AbstractMap.SimpleEntry;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatExtensionMapper;
import io.cloudevents.v02.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.2
*/
public class ExtensionMapper {
private ExtensionMapper() {}
private static final List<String> RESERVED_HEADERS =
ContextAttributes.VALUES.stream()
.map(attribute -> AttributeMapper
.HEADER_PREFIX + attribute)
.collect(Collectors.toList());
static {
RESERVED_HEADERS.add("content-type");
};
/**
* Following the signature of {@link FormatExtensionMapper}
* @param headers The HTTP headers
* @return The potential extensions without parsing
*/
public static Map<String, String> map(Map<String, Object> headers) {
Objects.requireNonNull(headers);
// remove all reserved words and the remaining may be extensions
return
headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue().toString()))
.filter(header -> {
return !RESERVED_HEADERS.contains(header.getKey());
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}

View File

@ -0,0 +1,78 @@
/**
* Copyright 2019 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.v02.http;
import static io.cloudevents.v02.http.AttributeMapper.HEADER_PREFIX;
import java.util.AbstractMap.SimpleEntry;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatHeaderMapper;
import io.cloudevents.v02.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.2
*/
public final class HeaderMapper {
private HeaderMapper() {}
private static final String HTTP_CONTENT_TYPE = "Content-Type";
/**
* Following the signature of {@link FormatHeaderMapper}
* @param attributes The map of attributes created by {@link AttributeMapper}
* @param extensions The map of extensions created by {@link ExtensionMapper}
* @return The map of HTTP Headers
*/
public static Map<String, String> map(Map<String, String> attributes,
Map<String, String> extensions) {
Objects.requireNonNull(attributes);
Objects.requireNonNull(extensions);
Map<String, String> result = attributes.entrySet()
.stream()
.filter(attribute -> null!= attribute.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.filter(header -> !header.getKey()
.equals(ContextAttributes.contenttype.name()))
.map(header -> new SimpleEntry<>(HEADER_PREFIX+header.getKey(),
header.getValue()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
result.putAll(
extensions.entrySet()
.stream()
.filter(extension -> null!= extension.getValue())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
);
Optional.ofNullable(attributes.get(ContextAttributes.contenttype.name()))
.ifPresent((ct) -> {
result.put(HTTP_CONTENT_TYPE, ct);
});
return result;
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright 2019 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.v02.http;
import java.util.HashMap;
import java.util.Map;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.BinaryMarshaller;
import io.cloudevents.format.StructuredMarshaller;
import io.cloudevents.format.Wire;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.json.Json;
import io.cloudevents.v02.Accessor;
import io.cloudevents.v02.AttributesImpl;
import io.cloudevents.v02.CloudEventImpl;
/**
*
* @author fabiojose
* @version 0.2
*/
public class Marshallers {
private Marshallers() {}
private static final Map<String, String> NO_HEADERS =
new HashMap<String, String>();
/**
* Builds a Binary Content Mode marshaller to marshal cloud events as JSON for
* HTTP Transport Binding
*
* @param <T> The 'data' type
* @return A step to provide the {@link CloudEventImpl} and marshal as JSON
* @see BinaryMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> binary() {
return
BinaryMarshaller.<AttributesImpl, T, String, String>
builder()
.map(AttributesImpl::marshal)
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map)
.map(Json.<T, String>marshaller()::marshal)
.builder(Wire<String, String, String>::new);
}
/**
* Builds a Structured Content Mode marshaller to marshal cloud event as JSON for
* HTTP Transport Binding
* @param <T> The 'data' type
* @return A step to provider the {@link CloudEventImpl} and marshal as JSON
* @see StructuredMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> structured() {
return
StructuredMarshaller.
<AttributesImpl, T, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((event) -> {
return Json.<CloudEvent<AttributesImpl, T>, String>
marshaller().marshal(event, NO_HEADERS);
})
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map);
}
}

View File

@ -0,0 +1,76 @@
package io.cloudevents.v02.http;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.format.BinaryUnmarshaller;
import io.cloudevents.format.StructuredUnmarshaller;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.json.Json;
import io.cloudevents.v02.AttributesImpl;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
/**
*
* @author fabiojose
* @version 0.2
*/
public class Unmarshallers {
private Unmarshallers() {}
/**
* Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> The 'data' type
* @param type The type reference to use for 'data' unmarshal
* @return A step to supply the headers, payload and to unmarshal
* @see BinaryUnmarshaller
*/
public static <T> HeadersStep<AttributesImpl, T, String>
binary(Class<T> type) {
return
BinaryUnmarshaller.<AttributesImpl, T, String>builder()
.map(AttributeMapper::map)
.map(AttributesImpl::unmarshal)
.map("application/json", Json.umarshaller(type)::unmarshal)
.next()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.builder(CloudEventBuilder.<T>builder()::build);
}
/**
* Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> The 'data' type
* @param typeOfData The type reference to use for 'data' unmarshal
* @return A step to supply the headers, payload and to unmarshal
* @see StructuredUnmarshaller
*/
public static <T> HeadersStep<AttributesImpl, T, String>
structured(Class<T> typeOfData) {
return
StructuredUnmarshaller.<AttributesImpl, T, String>
builder()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.map((payload, extensions) -> {
CloudEventImpl<T> event =
Json.<CloudEventImpl<T>>
decodeValue(payload, CloudEventImpl.class, typeOfData);
CloudEventBuilder<T> builder =
CloudEventBuilder.<T>builder(event);
extensions.get().forEach(extension -> {
builder.withExtension(extension);
});
return builder.build();
});
}
}

View File

@ -0,0 +1,45 @@
package io.cloudevents.v03;
import java.util.Collection;
import java.util.Objects;
import io.cloudevents.Attributes;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.fun.ExtensionFormatAccessor;
/**
*
* @author fabiojose
*
*/
public class Accessor {
private Accessor() {}
/**
* To get access the set of {@link ExtensionFormat} inside the
* event.
*
* <br>
* <br>
* This method follow the signature of
* {@link ExtensionFormatAccessor#extensionsOf(CloudEvent)}
*
* @param cloudEvent
* @throws IllegalArgumentException When argument is not an instance
* of {@link CloudEventImpl}
*/
@SuppressWarnings({"unchecked", "rawtypes"})
public static <A extends Attributes, T> Collection<ExtensionFormat>
extensionsOf(CloudEvent<A, T> cloudEvent) {
Objects.requireNonNull(cloudEvent);
if(cloudEvent instanceof CloudEventImpl) {
CloudEventImpl impl = (CloudEventImpl)cloudEvent;
return impl.getExtensionsFormats();
}
throw new IllegalArgumentException("Invalid instance type: "
+ cloudEvent.getClass());
}
}

View File

@ -0,0 +1,230 @@
/**
* Copyright 2019 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.v03;
import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
import java.net.URI;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import io.cloudevents.Attributes;
import io.cloudevents.json.ZonedDateTimeDeserializer;
/**
* The event attributes implementation for v0.3
*
* @author fabiojose
*
*/
@JsonInclude(value = Include.NON_ABSENT)
public class AttributesImpl implements Attributes {
@NotBlank
private final String id;
@NotNull
private final URI source;
@NotBlank
@Pattern(regexp = "0\\.3")
private final String specversion;
@NotBlank
private final String type;
@JsonDeserialize(using = ZonedDateTimeDeserializer.class)
private final ZonedDateTime time;
private final URI schemaurl;
@Pattern(regexp = "base64")
private final String datacontentencoding;
private final String datacontenttype;
@Size(min = 1)
private final String subject;
AttributesImpl(String id, URI source, String specversion, String type,
ZonedDateTime time, URI schemaurl, String datacontentencoding,
String datacontenttype, String subject) {
this.id = id;
this.source = source;
this.specversion = specversion;
this.type = type;
this.time = time;
this.schemaurl = schemaurl;
this.datacontentencoding = datacontentencoding;
this.datacontenttype = datacontenttype;
this.subject = subject;
}
public String getId() {
return id;
}
public URI getSource() {
return source;
}
public String getSpecversion() {
return specversion;
}
public String getType() {
return type;
}
public Optional<ZonedDateTime> getTime() {
return Optional.ofNullable(time);
}
public Optional<URI> getSchemaurl() {
return Optional.ofNullable(schemaurl);
}
public Optional<String> getDatacontentencoding() {
return Optional.ofNullable(datacontentencoding);
}
public Optional<String> getDatacontenttype() {
return Optional.ofNullable(datacontenttype);
}
/**
* {@inheritDoc}
*/
public Optional<String> getMediaType() {
return getDatacontenttype();
}
public Optional<String> getSubject() {
return Optional.ofNullable(subject);
}
@Override
public String toString() {
return "AttributesImpl [id=" + id + ", source=" + source
+ ", specversion=" + specversion + ", type=" + type
+ ", time=" + time + ", schemaurl=" + schemaurl
+ ", datacontentencoding=" + datacontentencoding
+ ", datacontenttype=" + datacontenttype + ", subject="
+ subject + "]";
}
/**
* Used by the Jackson framework to unmarshall.
*/
@JsonCreator
public static AttributesImpl build(
@JsonProperty("id") String id,
@JsonProperty("source") URI source,
@JsonProperty("specversion") String specversion,
@JsonProperty("type") String type,
@JsonProperty("time") ZonedDateTime time,
@JsonProperty("schemaurl") URI schemaurl,
@JsonProperty("datacontentenconding") String datacontentencoding,
@JsonProperty("datacontenttype") String datacontenttype,
@JsonProperty("subject") String subject) {
return new AttributesImpl(id, source, specversion, type, time,
schemaurl, datacontentencoding, datacontenttype, subject);
}
/**
* Creates the marshaller instance to marshall {@link AttributesImpl} as
* a {@link Map} of strings
*/
public static Map<String, String> marshal(AttributesImpl attributes) {
Objects.requireNonNull(attributes);
Map<String, String> result = new HashMap<>();
result.put(ContextAttributes.type.name(),
attributes.getType());
result.put(ContextAttributes.specversion.name(),
attributes.getSpecversion());
result.put(ContextAttributes.source.name(),
attributes.getSource().toString());
result.put(ContextAttributes.id.name(),
attributes.getId());
attributes.getTime().ifPresent((value) -> {
result.put(ContextAttributes.time.name(),
value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
});
attributes.getSchemaurl().ifPresent((schema) -> {
result.put(ContextAttributes.schemaurl.name(),
schema.toString());
});
attributes.getDatacontenttype().ifPresent((ct) -> {
result.put(ContextAttributes.datacontenttype.name(), ct);
});
attributes.getDatacontentencoding().ifPresent(dce -> {
result.put(ContextAttributes.datacontentencoding.name(), dce);
});
attributes.getSubject().ifPresent(subject -> {
result.put(ContextAttributes.subject.name(), subject);
});
return result;
}
/**
* The attribute unmarshaller for the binary format, that receives a
* {@code Map} with attributes names as String and value as String.
*/
public static AttributesImpl unmarshal(Map<String, String> attributes) {
String type = attributes.get(ContextAttributes.type.name());
ZonedDateTime time =
Optional.ofNullable(attributes.get(ContextAttributes.time.name()))
.map((t) -> ZonedDateTime.parse(t,
ISO_ZONED_DATE_TIME))
.orElse(null);
String specversion = attributes.get(ContextAttributes.specversion.name());
URI source = URI.create(attributes.get(ContextAttributes.source.name()));
URI schemaurl =
Optional.ofNullable(attributes.get(ContextAttributes.schemaurl.name()))
.map(schema -> URI.create(schema))
.orElse(null);
String id = attributes.get(ContextAttributes.id.name());
String datacontenttype =
attributes.get(ContextAttributes.datacontenttype.name());
String datacontentencoding =
attributes.get(ContextAttributes.datacontentencoding.name());
String subject = attributes.get(ContextAttributes.subject.name());
return AttributesImpl.build(id, source, specversion, type,
time, schemaurl, datacontentencoding,
datacontenttype, subject);
}
}

View File

@ -0,0 +1,268 @@
/**
* Copyright 2019 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.v03;
import static java.lang.String.format;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collection;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.fun.EventBuilder;
/**
* The event builder.
*
* @author fabiojose
*
*/
public final class CloudEventBuilder<T> implements
EventBuilder<T, AttributesImpl> {
private CloudEventBuilder() {}
private static Validator VALIDATOR;
public static final String SPEC_VERSION = "0.3";
private static final String MESSAGE_SEPARATOR = ", ";
private static final String MESSAGE = "'%s' %s";
private static final String ERR_MESSAGE = "invalid payload: %s";
private String id;
private URI source;
private String type;
private ZonedDateTime time;
private URI schemaurl;
private String datacontentencoding;
private String datacontenttype;
private String subject;
private T data;
private final Set<ExtensionFormat> extensions = new HashSet<>();
private static Validator getValidator() {
if(null== VALIDATOR) {
VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
}
return VALIDATOR;
}
/**
* Gets a brand new builder instance
* @param <T> The 'data' type
*/
public static <T> CloudEventBuilder<T> builder() {
return new CloudEventBuilder<T>();
}
public static <T> CloudEventBuilder<T> builder(
CloudEvent<AttributesImpl, T> base) {
Objects.requireNonNull(base);
CloudEventBuilder<T> result = new CloudEventBuilder<>();
AttributesImpl attributes = base.getAttributes();
result
.withId(attributes.getId())
.withSource(attributes.getSource())
.withType(attributes.getType());
attributes.getTime().ifPresent(time -> {
result.withTime(time);
});
attributes.getSchemaurl().ifPresent((schema) -> {
result.withSchemaurl(schema);
});
attributes.getDatacontenttype().ifPresent(dc -> {
result.withDatacontenttype(dc);
});
attributes.getDatacontentencoding().ifPresent(dce -> {
result.withDatacontentencoding(dce);
});
attributes.getSubject().ifPresent(subject -> {
result.withSubject(subject);
});
Accessor.extensionsOf(base)
.forEach(extension -> {
result.withExtension(extension);
});
base.getData().ifPresent(data -> {
result.withData(data);
});
return result;
}
/**
* Build an event from data and attributes
* @param <T> the type of 'data'
* @param data the value of data
* @param attributes the context attributes
* @return An new {@link CloudEventImpl} immutable instance
* @throws IllegalStateException When there are specification constraints
* violations
*/
public static <T> CloudEventImpl<T> of(T data, AttributesImpl attributes,
Collection<ExtensionFormat> extensions) {
CloudEventBuilder<T> builder = CloudEventBuilder.<T>builder()
.withId(attributes.getId())
.withSource(attributes.getSource())
.withType(attributes.getType());
attributes.getTime().ifPresent((time) -> {
builder.withTime(time);
});
attributes.getSchemaurl().ifPresent((schemaurl) -> {
builder.withSchemaurl(schemaurl);
});
attributes.getDatacontentencoding().ifPresent((dce) -> {
builder.withDatacontentencoding(dce);
});
attributes.getDatacontenttype().ifPresent((dct) -> {
builder.withDatacontenttype(dct);
});
attributes.getSubject().ifPresent((subject) -> {
builder.withSubject(subject);
});
extensions.stream()
.forEach(extension -> {
builder.withExtension(extension);
});
builder.withData(data);
return builder.build();
}
@Override
public CloudEvent<AttributesImpl, T> build(T data, AttributesImpl attributes,
Collection<ExtensionFormat> extensions){
return CloudEventBuilder.<T>of(data, attributes, extensions);
}
/**
*
* @return An new {@link CloudEvent} immutable instance
* @throws IllegalStateException When there are specification constraints
* violations
*/
public CloudEventImpl<T> build() {
AttributesImpl attributes = new AttributesImpl(id, source, SPEC_VERSION,
type, time, schemaurl, datacontentencoding, datacontenttype,
subject);
CloudEventImpl<T> cloudEvent =
new CloudEventImpl<T>(attributes, data, extensions);
Set<ConstraintViolation<Object>> violations =
getValidator().validate(cloudEvent);
violations.addAll(getValidator().validate(cloudEvent.getAttributes()));
final String errs =
violations.stream()
.map(v -> format(MESSAGE, v.getPropertyPath(), v.getMessage()))
.collect(Collectors.joining(MESSAGE_SEPARATOR));
Optional.ofNullable(
"".equals(errs) ? null : errs
).ifPresent((e) -> {
throw new IllegalStateException(format(ERR_MESSAGE, e));
});
return cloudEvent;
}
public CloudEventBuilder<T> withId(String id) {
this.id = id;
return this;
}
public CloudEventBuilder<T> withSource(URI source) {
this.source = source;
return this;
}
public CloudEventBuilder<T> withType(String type) {
this.type = type;
return this;
}
public CloudEventBuilder<T> withTime(ZonedDateTime time) {
this.time = time;
return this;
}
public CloudEventBuilder<T> withSchemaurl(URI schemaurl) {
this.schemaurl = schemaurl;
return this;
}
public CloudEventBuilder<T> withDatacontentencoding(
String datacontentencoding) {
this.datacontentencoding = datacontentencoding;
return this;
}
public CloudEventBuilder<T> withDatacontenttype(
String datacontenttype) {
this.datacontenttype = datacontenttype;
return this;
}
public CloudEventBuilder<T> withSubject(
String subject) {
this.subject = subject;
return this;
}
public CloudEventBuilder<T> withData(T data) {
this.data = data;
return this;
}
public CloudEventBuilder<T> withExtension(ExtensionFormat extension) {
this.extensions.add(extension);
return this;
}
}

View File

@ -0,0 +1,139 @@
/**
* Copyright 2019 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.v03;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.constraints.NotNull;
import com.fasterxml.jackson.annotation.JsonAnyGetter;
import com.fasterxml.jackson.annotation.JsonAnySetter;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.extensions.InMemoryFormat;
/**
* The event implementation
*
* @author fabiojose
*
*/
@JsonInclude(value = Include.NON_ABSENT)
public class CloudEventImpl<T> implements CloudEvent<AttributesImpl, T> {
@JsonIgnore
@NotNull
private final AttributesImpl attributes;
private final T data;
@NotNull
private final Map<String, Object> extensions;
private final Set<ExtensionFormat> extensionsFormats;
CloudEventImpl(AttributesImpl attributes, T data,
Set<ExtensionFormat> extensions) {
this.attributes = attributes;
this.data = data;
this.extensions = extensions.stream()
.map(ExtensionFormat::memory)
.collect(Collectors.toMap(InMemoryFormat::getKey,
InMemoryFormat::getValue));
this.extensionsFormats = extensions;
}
/**
* Used by the {@link Accessor} to access the set of {@link ExtensionFormat}
*/
Set<ExtensionFormat> getExtensionsFormats() {
return extensionsFormats;
}
@JsonUnwrapped
@Override
public AttributesImpl getAttributes() {
return attributes;
}
@Override
public Optional<T> getData() {
return Optional.ofNullable(data);
}
@JsonAnyGetter
@Override
public Map<String, Object> getExtensions() {
return Collections.unmodifiableMap(extensions);
}
/**
* The unique method that allows mutation. Used by
* Jackson Framework to inject the extensions.
*
* @param name Extension name
* @param value Extension value
*/
@JsonAnySetter
void addExtension(String name, Object value) {
extensions.put(name, value);
}
/**
* Used by the Jackson Framework to unmarshall.
*/
@JsonCreator
public static <T> CloudEventImpl<T> build(
@JsonProperty("id") String id,
@JsonProperty("source") URI source,
@JsonProperty("specversion") String specversion,
@JsonProperty("type") String type,
@JsonProperty("time") ZonedDateTime time,
@JsonProperty("schemaurl") URI schemaurl,
@JsonProperty("datacontentencoding") String datacontentencoding,
@JsonProperty("datacontenttype") String datacontenttype,
@JsonProperty("subject") String subject,
@JsonProperty("data") T data) {
return CloudEventBuilder.<T>builder()
.withId(id)
.withSource(source)
.withType(type)
.withTime(time)
.withSchemaurl(schemaurl)
.withDatacontentencoding(datacontentencoding)
.withDatacontenttype(datacontenttype)
.withData(data)
.withSubject(subject)
.build();
}
}

View File

@ -0,0 +1,45 @@
/**
* Copyright 2019 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.v03;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* The specification reserved words: the context attributes
*
* @author fabiojose
*
*/
public enum ContextAttributes {
id,
source,
specversion,
type,
time,
schemaurl,
datacontenttype,
datacontentencoding,
subject;
public static final List<String> VALUES =
Arrays.asList(ContextAttributes.values())
.stream()
.map(Enum::name)
.collect(Collectors.toList());
}

View File

@ -0,0 +1,80 @@
/**
* Copyright 2019 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.v03.http;
import static java.util.stream.Collectors.toMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicReference;
import io.cloudevents.fun.BinaryFormatAttributeMapper;
import io.cloudevents.v03.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.3
*/
public class AttributeMapper {
private AttributeMapper() {}
static final String HEADER_PREFIX = "ce-";
/**
* Following the signature of {@link BinaryFormatAttributeMapper#map(Map)}
* @param headers Map of HTTP request
* @return Map with spec attributes and values without parsing
* @see ContextAttributes
*/
public static Map<String, String> map(final Map<String, Object> headers) {
Objects.requireNonNull(headers);
final AtomicReference<Optional<Entry<String, Object>>> ct =
new AtomicReference<>();
ct.set(Optional.empty());
Map<String, String> result = headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.peek(header -> {
if("content-type".equals(header.getKey())) {
ct.set(Optional.ofNullable(header));
}
})
.filter(header -> header.getKey().startsWith(HEADER_PREFIX))
.map(header -> new SimpleEntry<>(header.getKey()
.substring(HEADER_PREFIX.length()), header.getValue()))
.map(header -> new SimpleEntry<>(header.getKey(),
header.getValue().toString()))
.collect(toMap(Entry::getKey, Entry::getValue));
ct.get().ifPresent(contentType -> {
result.put(ContextAttributes.datacontenttype.name(),
contentType.getValue().toString());
});
return result;
}
}

View File

@ -0,0 +1,67 @@
/**
* Copyright 2019 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.v03.http;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatExtensionMapper;
import io.cloudevents.v03.ContextAttributes;
/**
*
* @author fabiojose
* @version 0.2
*/
public class ExtensionMapper {
private ExtensionMapper() {}
private static final List<String> RESERVED_HEADERS =
ContextAttributes.VALUES.stream()
.map(attribute -> AttributeMapper
.HEADER_PREFIX + attribute)
.collect(Collectors.toList());
static {
RESERVED_HEADERS.add("content-type");
};
/**
* Following the signature of {@link FormatExtensionMapper}
* @param headers The HTTP headers
* @return The potential extensions without parsing
*/
public static Map<String, String> map(Map<String, Object> headers) {
Objects.requireNonNull(headers);
// remove all reserved words and the remaining may be extensions
return
headers.entrySet()
.stream()
.filter(header -> null!= header.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue().toString()))
.filter(header -> {
return !RESERVED_HEADERS.contains(header.getKey());
})
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}
}

View File

@ -0,0 +1,79 @@
/**
* Copyright 2019 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.v03.http;
import static io.cloudevents.v03.http.AttributeMapper.HEADER_PREFIX;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.AbstractMap.SimpleEntry;
import java.util.Map.Entry;
import java.util.stream.Collectors;
import io.cloudevents.fun.FormatHeaderMapper;
import io.cloudevents.v03.ContextAttributes;
/**
*
* @author fabiojose
*
*/
public class HeaderMapper {
private HeaderMapper() {}
private static final String HTTP_CONTENT_TYPE = "Content-Type";
/**
* Following the signature of {@link FormatHeaderMapper}
* @param attributes The map of attributes created by {@link AttributeMapper}
* @param extensions The map of extensions created by {@link ExtensionMapper}
* @return The map of HTTP Headers
*/
public static Map<String, String> map(Map<String, String> attributes,
Map<String, String> extensions) {
Objects.requireNonNull(attributes);
Objects.requireNonNull(extensions);
Map<String, String> result = attributes.entrySet()
.stream()
.filter(attribute -> null!= attribute.getValue())
.map(header -> new SimpleEntry<>(header.getKey()
.toLowerCase(Locale.US), header.getValue()))
.filter(header -> !header.getKey()
.equals(ContextAttributes.datacontenttype.name()))
.map(header -> new SimpleEntry<>(HEADER_PREFIX+header.getKey(),
header.getValue()))
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));
result.putAll(
extensions.entrySet()
.stream()
.filter(extension -> null!= extension.getValue())
.collect(Collectors.toMap(Entry::getKey, Entry::getValue))
);
Optional.ofNullable(attributes
.get(ContextAttributes.datacontenttype.name()))
.ifPresent((dct) -> {
result.put(HTTP_CONTENT_TYPE, dct);
});
return result;
}
}

View File

@ -0,0 +1,83 @@
/**
* Copyright 2019 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.v03.http;
import java.util.HashMap;
import java.util.Map;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.BinaryMarshaller;
import io.cloudevents.format.StructuredMarshaller;
import io.cloudevents.format.Wire;
import io.cloudevents.format.builder.EventStep;
import io.cloudevents.json.Json;
import io.cloudevents.v03.Accessor;
import io.cloudevents.v03.AttributesImpl;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
* @version 0.3
*/
public class Marshallers {
private Marshallers() {}
private static final Map<String, String> NO_HEADERS =
new HashMap<String, String>();
/**
* Builds a Binary Content Mode marshaller to marshal cloud events as JSON for
* HTTP Transport Binding
*
* @param <T> The 'data' type
* @return A step to provide the {@link CloudEventImpl} and marshal as JSON
* @see BinaryMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> binary() {
return
BinaryMarshaller.<AttributesImpl, T, String, String>
builder()
.map(AttributesImpl::marshal)
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map)
.map(Json.<T, String>marshaller()::marshal)
.builder(Wire<String, String, String>::new);
}
/**
* Builds a Structured Content Mode marshaller to marshal cloud event as JSON for
* HTTP Transport Binding
* @param <T> The 'data' type
* @return A step to provider the {@link CloudEventImpl} and marshal as JSON
* @see StructuredMarshaller
*/
public static <T> EventStep<AttributesImpl, T, String, String> structured() {
return
StructuredMarshaller.
<AttributesImpl, T, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((event) -> {
return Json.<CloudEvent<AttributesImpl, T>, String>
marshaller().marshal(event, NO_HEADERS);
})
.map(Accessor::extensionsOf)
.map(ExtensionFormat::marshal)
.map(HeaderMapper::map);
}
}

View File

@ -0,0 +1,91 @@
/**
* Copyright 2019 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.v03.http;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.format.BinaryUnmarshaller;
import io.cloudevents.format.StructuredUnmarshaller;
import io.cloudevents.format.builder.HeadersStep;
import io.cloudevents.json.Json;
import io.cloudevents.v03.AttributesImpl;
import io.cloudevents.v03.CloudEventBuilder;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
* @version 0.3
*/
public class Unmarshallers {
private Unmarshallers() {}
/**
* Builds a Binary Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> The 'data' type
* @param type The type reference to use for 'data' unmarshal
* @return A step to supply the headers, payload and to unmarshal
* @see BinaryUnmarshaller
*/
public static <T> HeadersStep<AttributesImpl, T, String>
binary(Class<T> type) {
return
BinaryUnmarshaller.<AttributesImpl, T, String>builder()
.map(AttributeMapper::map)
.map(AttributesImpl::unmarshal)
.map("application/json", Json.umarshaller(type)::unmarshal)
.next()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.builder(CloudEventBuilder.<T>builder()::build);
}
/**
* Builds a Structured Content Mode unmarshaller to unmarshal JSON as CloudEvents data
* for HTTP Transport Binding
*
* @param <T> The 'data' type
* @param typeOfData The type reference to use for 'data' unmarshal
* @return A step to supply the headers, payload and to unmarshal
* @see StructuredUnmarshaller
*/
public static <T> HeadersStep<AttributesImpl, T, String>
structured(Class<T> typeOfData) {
return
StructuredUnmarshaller.<AttributesImpl, T, String>
builder()
.map(ExtensionMapper::map)
.map(DistributedTracingExtension::unmarshall)
.next()
.map((payload, extensions) -> {
CloudEventImpl<T> event =
Json.<CloudEventImpl<T>>
decodeValue(payload, CloudEventImpl.class, typeOfData);
CloudEventBuilder<T> builder =
CloudEventBuilder.<T>builder(event);
extensions.get().forEach(extension -> {
builder.withExtension(extension);
});
return builder.build();
});
}
}

View File

@ -1,198 +0,0 @@
/**
* Copyright 2018 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;
import io.cloudevents.extensions.DistributedTracingExtension;
import org.junit.Test;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Map;
import java.util.HashMap;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
public class CloudEventBuilderTest {
@Test
public void testBuilderWithData() {
// given
final Map<String, String> keyValueStore = new HashMap<>();
keyValueStore.put("key1", "value1");
keyValueStore.put("key2", "value2");
final String id = UUID.randomUUID().toString();
final URI src = URI.create("/trigger");
final String type = "My.Cloud.Event.Type";
final ZonedDateTime eventTime = ZonedDateTime.now();
final String contentType = "application/json";
final URI schemaUri = URI.create("http://cloudevents.io/schema");
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.data(keyValueStore)
.contentType(contentType)
.type(type)
.schemaURL(schemaUri)
.time(eventTime)
.id(id)
.source(src)
.build();
// than
simpleKeyValueEvent.getData().ifPresent(data -> {
assertThat(data).isNotNull();
assertThat(data).containsKeys("key1", "key2");
assertThat(data).containsValues("value1", "value2");
});
assertThat(simpleKeyValueEvent.getContentType().get()).isEqualTo(contentType);
assertThat(simpleKeyValueEvent.getTime().get()).isEqualTo(eventTime);
assertThat(simpleKeyValueEvent.getId()).isEqualTo(id);
assertThat(simpleKeyValueEvent.getSchemaURL().get()).isEqualTo(schemaUri);
assertThat(simpleKeyValueEvent.getType()).isEqualTo(type);
assertThat(simpleKeyValueEvent.getSource()).isEqualTo(src);
assertThat(simpleKeyValueEvent.getSpecVersion()).isEqualTo(SpecVersion.DEFAULT.toString());
}
@Test
public void testBuilderWithoutData() {
// given
final String id = UUID.randomUUID().toString();
final URI src = URI.create("/trigger");
final String type = "My.Cloud.Event.Type";
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.type(type)
.id(id)
.source(src)
.build();
// than
assertThat(simpleKeyValueEvent.getData().isPresent()).isFalse();
assertThat(simpleKeyValueEvent.getTime().isPresent()).isFalse();
assertThat(simpleKeyValueEvent.getId()).isEqualTo(id);
assertThat(simpleKeyValueEvent.getType()).isEqualTo(type);
assertThat(simpleKeyValueEvent.getSource()).isEqualTo(src);
assertThat(simpleKeyValueEvent.getSpecVersion()).isEqualTo(SpecVersion.DEFAULT.toString());
}
@Test
public void testBuilderWithoutDataAndUrn() {
// given
final String id = UUID.randomUUID().toString();
final URI src = URI.create("urn:event:from:myapi/resourse/123");
final String type = "some.Cloud.Event.Type";
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.type(type)
.id(id)
.source(src)
.build();
// than
assertThat(simpleKeyValueEvent.getSource()).isEqualTo(src);
}
@Test
public void test01BuilderWithoutDataAndUrn() {
// given
final String id = UUID.randomUUID().toString();
final URI src = URI.create("urn:event:from:myapi/resourse/123");
final String type = "some.Cloud.Event.Type";
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.specVersion("0.1")
.type(type)
.id(id)
.source(src)
.build();
// than
assertThat(simpleKeyValueEvent.getSource()).isEqualTo(src);
assertThat(simpleKeyValueEvent.getSpecVersion()).isEqualTo(SpecVersion.V_01.toString());
}
@Test
public void testBuilderWithoutDataAndURISchema() {
// given
final String id = UUID.randomUUID().toString();
final URI src = URI.create("urn:event:from:myapi/resourse/123");
final String type = "some.Cloud.Event.Type";
final URI schema = URI.create("urn:oasis:names:specification:docbook:dtd:xml:4.1.2");
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.type(type)
.id(id)
.source(src)
.schemaURL(schema)
.build();
// than
assertThat(simpleKeyValueEvent.getSchemaURL().get()).isEqualTo(schema);
}
@Test
public void testBuilderWithoutDataAndMailto() {
// given
final String id = UUID.randomUUID().toString();
final URI src = URI.create("mailto:cncf-wg-serverless@lists.cncf.io");
final String type = "My.Cloud.Event.Type";
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.type(type)
.id(id)
.source(src)
.build();
// than
assertThat(simpleKeyValueEvent.getSource()).isEqualTo(src);
}
@Test
public void testBuilderWithoutDataAndDistributedTracingExtension() {
// given
final String id = UUID.randomUUID().toString();
final URI src = URI.create("mailto:cncf-wg-serverless@lists.cncf.io");
final String type = "My.Cloud.Event.Type";
final DistributedTracingExtension dte = new DistributedTracingExtension();
dte.setTraceparent("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01");
dte.setTracestate("congo=BleGNlZWRzIHRohbCBwbGVhc3VyZS4");
// when
final CloudEvent<Map<String, String>> simpleKeyValueEvent = new CloudEventBuilder()
.type(type)
.id(id)
.source(src)
.extension(dte)
.build();
// than
assertThat(simpleKeyValueEvent.getSource()).isEqualTo(src);
assertThat(simpleKeyValueEvent.getExtensions().get()).contains(dte);
Extension receivedDte = simpleKeyValueEvent.getExtensions().get().get(0);
assertThat(receivedDte).extracting("traceparent", "tracestate")
.contains("00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01", "congo=BleGNlZWRzIHRohbCBwbGVhc3VyZS4");
}
}

View File

@ -1,76 +0,0 @@
/**
* Copyright 2018 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;
import io.cloudevents.json.Json;
import org.junit.Test;
import java.net.URI;
import java.time.ZonedDateTime;
import java.util.Map;
import static java.time.format.DateTimeFormatter.ISO_ZONED_DATE_TIME;
import static org.assertj.core.api.Assertions.assertThat;
public class CloudEventJacksonTest {
@Test
public void testParseAzure01JSON() {
CloudEvent<Map<String, ?>> ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("01_azure.json"));
assertThat(ce.getSpecVersion()).isEqualTo(SpecVersion.V_01.toString());
assertAzureCloudEvent(ce);
}
@Test
public void testParseAzure02JSON() {
CloudEvent<Map<String, ?>> ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("02_azure.json"));
assertThat(ce.getSpecVersion()).isEqualTo(SpecVersion.V_02.toString());
assertAzureCloudEvent(ce);
}
private void assertAzureCloudEvent(CloudEvent<Map<String, ?>> ce) {
assertThat(ce.getType()).isEqualTo("Microsoft.Storage.BlobCreated");
ce.getData().ifPresent(data -> {
assertThat(Map.class).isAssignableFrom(data.getClass());
assertThat(data.get("clientRequestId")).isEqualTo("a23b4aba-2755-4107-8020-8ba6c54b203d");
assertThat(Map.class).isAssignableFrom(data.get("storageDiagnostics").getClass());
Map<String, String> storageDiagnostics = (Map<String, String>) data.get("storageDiagnostics");
assertThat(storageDiagnostics).containsOnlyKeys("batchId");
assertThat(storageDiagnostics.get("batchId")).isEqualTo("ba4fb664-f289-4742-8067-6c859411b066");
});
}
@Test
public void testParseAmazon01JSON() {
CloudEvent ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("01_aws.json"));
assertAmazonCloudEvent(ce);
}
@Test
public void testParseAmazon02JSON() {
CloudEvent ce = Json.fromInputStream(Thread.currentThread().getContextClassLoader().getResourceAsStream("02_aws.json"));
assertAmazonCloudEvent(ce);
}
private void assertAmazonCloudEvent(CloudEvent ce) {
assertThat(ce.getType()).isEqualTo("aws.s3.object.created");
assertThat(ce.getId()).isEqualTo("C234-1234-1234");
assertThat(ce.getData().isPresent());
assertThat(ce.getSource().equals(URI.create("https://serverless.com")));
assertThat(ce.getTime().get()).isEqualTo(ZonedDateTime.parse("2018-04-26T14:48:09.769Z", ISO_ZONED_DATE_TIME));
}
}

View File

@ -0,0 +1,66 @@
/**
* Copyright 2019 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.extensions;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
/**
*
* @author fabiojose
*
*/
public class DistributedTracingExtensionTest {
@Test
public void should_transport_format_ok() {
// setup
DistributedTracingExtension tracing = new DistributedTracingExtension();
tracing.setTraceparent("parent");
tracing.setTracestate("state");
// act
ExtensionFormat format =
new DistributedTracingExtension.Format(tracing);
// assert
assertEquals("parent", format.transport().get("traceparent"));
assertEquals("state", format.transport().get("tracestate"));
}
@Test
public void should_inmemory_format_ok() {
// setup
DistributedTracingExtension tracing = new DistributedTracingExtension();
tracing.setTraceparent("parent");
tracing.setTracestate("state");
// act
ExtensionFormat format =
new DistributedTracingExtension.Format(tracing);
// assert
assertEquals("distributedTracing", format.memory().getKey());
assertEquals(DistributedTracingExtension.class, format.memory().getValueType());
assertEquals("parent",
((DistributedTracingExtension)format.memory().getValue()).getTraceparent());
assertEquals("state",
((DistributedTracingExtension)format.memory().getValue()).getTracestate());
}
}

View File

@ -0,0 +1,207 @@
/**
* Copyright 2019 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.format;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.Attributes;
import io.cloudevents.json.types.Much;
/**
*
* @author fabiojose
*
*/
public class StructuredMarshallerTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void should_throw_on_null_envelope_mime_header() {
// setup
expectedEx.expect(NullPointerException.class);
// act
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime(null, "");
}
@Test
public void should_throw_on_null_envelope_mime_value() {
// setup
expectedEx.expect(NullPointerException.class);
// act
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("", null);
}
@Test
public void should_be_ok_on_the_first_step() {
// act
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json");
}
@Test
public void should_throw_on_null_marshaller_step() {
// setup
expectedEx.expect(NullPointerException.class);
// act
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map(null);
}
@Test
public void should_throw_on_null_event() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.skip()
.withEvent(null);
}
@Test
public void should_be_ok_on_the_third_step() {
// act
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.skip()
.withEvent(() -> null);
}
@Test
public void should_throw_on_null_extension_accessor() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.map(null);
}
@Test
public void should_ok_on_the_extension_acessor() {
// act
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.map((event) -> {
return null;
});
}
@Test
public void should_throw_on_null_extension_marshaller() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.map((event) -> {
return null;
})
.map(null);
}
@Test
public void should_ok_on_extension_marshaller() {
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.map((event) -> {
return null;
})
.map((extensions) -> {
return null;
});
}
@Test
public void should_throw_on_null_header_mapper() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.map((event) -> {
return null;
})
.map((extensions) -> {
return null;
})
.map(null);
}
@Test
public void should_ok_on_header_mapper() {
StructuredMarshaller.<Attributes, Much, String, String>builder()
.mime("Content-Type", "application/cloudevents+json")
.map((ce) -> {
return null;
})
.map((event) -> {
return null;
})
.map((extensions) -> {
return null;
})
.map((attributes, extensions) -> null);
}
}

View File

@ -0,0 +1,151 @@
/**
* Copyright 2019 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.format;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.Attributes;
import io.cloudevents.json.types.Much;
/**
*
* @author fabiojose
*
*/
public class StructuredUnmarshallerTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void should_throw_on_null_extension_mapper() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map(null);
}
@Test
public void should_ok_on_extension_mapper() {
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> {
return null;
});
}
@Test
public void should_throw_on_null_extension_unmarshaller() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> {
return null;
})
.map(null);
}
@Test
public void should_ok_on_extension_unmarshaller() {
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> {
return null;
})
.map((extensions) -> {
return null;
});
}
@Test
public void should_throw_on_null_envelope_unmarshaller() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> {
return null;
})
.map((extensions) -> {
return null;
})
.next()
.map(null);
}
@Test
public void should_ok_on_envelope_unmarshaller() {
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> null)
.map((extensions) -> null)
.next()
.map((payload, extensions) -> null);
}
@Test
public void should_throw_on_null_headers() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> null)
.map((extensions) -> null)
.next()
.map((payload, extensions) -> null)
.withHeaders(null);
}
@Test
public void should_ok_on_headers() {
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> null)
.map((extensions) -> null)
.next()
.map((payload, extensions) -> null)
.withHeaders(() -> null);
}
@Test
public void should_throw_on_null_payload_supplier() {
// setup
expectedEx.expect(NullPointerException.class);
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> null)
.map((extensions) -> null)
.next()
.map((payload, extensions) -> null)
.withHeaders(() -> null)
.withPayload(null);
}
@Test
public void should_ok_on_payload_supplier() {
StructuredUnmarshaller.<Attributes, Much, String>builder()
.map((headers) -> null)
.map((extensions) -> null)
.next()
.map((payload, extensions) -> null)
.withHeaders(() -> null)
.withPayload(() -> null);
}
}

View File

@ -0,0 +1,79 @@
/**
* Copyright 2019 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.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author fabiojose
*
*/
public class WireTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void throws_error_when_null_headers() {
// setup
expectedEx.expect(NullPointerException.class);
new Wire<String, String, Object>("payload", null);
}
@Test
public void throws_when_try_to_change_headers() {
// setup
expectedEx.expect(UnsupportedOperationException.class);
Map<String, Object> headers = new HashMap<>();
headers.put("contenttype", "application/json");
// act
Wire<String, String, Object> wire = new Wire<>("payload", headers);
wire.getHeaders().put("my-header", "my-header-val");
}
@Test
public void should_ok_when_null_payload() {
Wire<String, String, Object> expected =
new Wire<>(null, new HashMap<>());
assertFalse(expected.getPayload().isPresent());
}
@Test
public void should_ok_when_payload_not_null() {
Wire<String, String, Object> actual =
new Wire<>("payload", new HashMap<>());
assertTrue(actual.getPayload().isPresent());
assertEquals("payload", actual.getPayload().get());
}
}

View File

@ -15,38 +15,92 @@
*/
package io.cloudevents.http;
import io.cloudevents.SpecVersion;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
public class HttpTransportAttributesTest {
@Test
public void testVersion01Headers() {
final HttpTransportAttributes v01 = HttpTransportAttributes.getHttpAttributesForSpec(SpecVersion.V_01);
assertThat(v01.specVersionKey()).isEqualTo("ce-cloudEventsVersion");
assertThat(v01.timeKey()).isEqualTo("ce-eventTime");
assertThat(v01.idKey()).isEqualTo("ce-eventID");
assertThat(v01.schemaUrlKey()).isEqualTo("ce-schemaURL");
assertThat(v01.typeKey()).isEqualTo("ce-eventType");
// non-changed between 01 / 02
assertThat(v01.sourceKey()).isEqualTo("ce-source");
}
@Test
public void testVersion02Headers() {
// setup
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
final HttpTransportAttributes v02 = HttpTransportAttributes.getHttpAttributesForSpec(SpecVersion.V_02);
assertThat(v02.specVersionKey()).isEqualTo("ce-specversion");
assertThat(v02.timeKey()).isEqualTo("ce-time");
assertThat(v02.idKey()).isEqualTo("ce-id");
assertThat(v02.schemaUrlKey()).isEqualTo("ce-schemaurl");
assertThat(v02.typeKey()).isEqualTo("ce-type");
// act
Map<String, String> attributes = io.cloudevents.v02.http.AttributeMapper.map(myHeaders);
// non-changed between 01 / 02
assertThat(v02.sourceKey()).isEqualTo("ce-source");
// assert
assertEquals("0x11", attributes.get("id"));
assertEquals("/source", attributes.get("source"));
assertEquals("0.2", attributes.get("specversion"));
assertEquals("br.my", attributes.get("type"));
assertEquals("2019-09-16T20:49:00Z", attributes.get("time"));
assertEquals("http://my.br", attributes.get("schemaurl"));
assertEquals("application/json", attributes.get("contenttype"));
}
@Test
public void shoul_map_attributes_v02() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("id", "0x11");
attributes.put("source", "/source");
attributes.put("specversion", "0.2");
attributes.put("type", "br.my");
attributes.put("time", "2019-09-16T20:49:00Z");
attributes.put("schemaurl", "http://my.br");
attributes.put("contenttype", "application/json");
// act
Map<String, String> headers = io.cloudevents.v02.http.HeaderMapper
.map(attributes, new HashMap<String, String>());
// assert
assertEquals("0x11", headers.get("ce-id"));
assertEquals("/source", headers.get("ce-source"));
assertEquals("0.2", headers.get("ce-specversion"));
assertEquals("br.my", headers.get("ce-type"));
assertEquals("2019-09-16T20:49:00Z", headers.get("ce-time"));
assertEquals("http://my.br", headers.get("ce-schemaurl"));
assertEquals("application/json", headers.get("Content-Type"));
}
@Test
public void should_map_headers_v03() {
// setup
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
myHeaders.put("ce-datacontentencoding", "base64");
myHeaders.put("ce-subject", "the subject");
// act
Map<String, String> attributes = io.cloudevents.v03.http.AttributeMapper.map(myHeaders);
// assert
assertEquals("0x11", attributes.get("id"));
assertEquals("/source", attributes.get("source"));
assertEquals("0.2", attributes.get("specversion"));
assertEquals("br.my", attributes.get("type"));
assertEquals("2019-09-16T20:49:00Z", attributes.get("time"));
assertEquals("http://my.br", attributes.get("schemaurl"));
assertEquals("application/json", attributes.get("datacontenttype"));
assertEquals("base64", attributes.get("datacontentencoding"));
assertEquals("the subject", attributes.get("subject"));
}
}

View File

@ -16,9 +16,10 @@
package io.cloudevents.json;
import com.fasterxml.jackson.core.type.TypeReference;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventBuilder;
import io.cloudevents.json.types.GlusterVolumeClaim;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
import org.junit.Test;
import java.io.IOException;
@ -35,22 +36,23 @@ public class CustomEventTypesTest {
// given
final Map<String, Object> storagePayload = (MAPPER.readValue(Thread.currentThread().getContextClassLoader().getResourceAsStream("pvc.json"), Map.class));
final CloudEvent<Map<String, Object>> storageCloudEventWrapper = new CloudEventBuilder<Map<String, Object>>()
.type("ProvisioningSucceeded")
.source(URI.create("/scheduler"))
.id(UUID.randomUUID().toString())
.data(storagePayload)
final CloudEventImpl<Map<String, Object>> storageCloudEventWrapper = CloudEventBuilder.<Map<String, Object>>builder()
.withType("ProvisioningSucceeded")
.withSource(URI.create("/scheduler"))
.withId(UUID.randomUUID().toString())
.withData(storagePayload)
.build();
// when
final String httpSerializedPayload = MAPPER.writeValueAsString(storageCloudEventWrapper);
assertThat(httpSerializedPayload).contains("PersistentVolumeClaim");
//PARSE into real object, on the other side
final CloudEvent<GlusterVolumeClaim> event = Json.decodeValue(httpSerializedPayload, new TypeReference<CloudEvent<GlusterVolumeClaim>>() {});
final CloudEventImpl<GlusterVolumeClaim> event = Json.decodeValue(httpSerializedPayload, new TypeReference<CloudEventImpl<GlusterVolumeClaim>>() {});
// then
assertThat(event.getData().get()).isNotNull();
assertThat(event.getData().get().getSpec().getCapacity().get("storage")).isEqualTo("2Gi");
assertThat(event.getData().get().getSpec().getAccessModes()).containsExactly("ReadWriteMany");
}
}

View File

@ -0,0 +1,76 @@
/**
* Copyright 2019 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.json;
import static org.junit.Assert.assertNull;
import java.util.Map;
import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference;
/**
*
* @author fabiojose
*
*/
public class JsonTest {
@Test
public void should_result_null_on_decode_type_empty_string() {
// setup
String payload = "";
// act
Object actual = Json.decodeValue(payload, Map.class);
// assert
assertNull(actual);
}
@Test
public void should_result_null_on_decode_type_null_string() {
// act
Object actual = Json.decodeValue(null, Map.class);
// assert
assertNull(actual);
}
@Test
public void should_result_null_on_decode_typereference_empty_string() {
// setup
String payload = "";
// act
Object actual = Json.decodeValue(payload, new TypeReference<Map<String, Object>>() {});
// assert
assertNull(actual);
}
@Test
public void should_result_null_on_decode_typereference_null_string() {
// act
Object actual = Json.decodeValue(null, new TypeReference<Map<String, Object>>() {});
// assert
assertNull(actual);
}
}

View File

@ -0,0 +1,49 @@
package io.cloudevents.json.types;
/**
*
* @author fabiojose
*
*/
public class Much {
private String wow;
public String getWow() {
return wow;
}
public void setWow(String wow) {
this.wow = wow;
}
@Override
public String toString() {
return "" + wow + "";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((wow == null) ? 0 : wow.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Much other = (Much) obj;
if (wow == null) {
if (other.wow != null)
return false;
} else if (!wow.equals(other.wow))
return false;
return true;
}
}

View File

@ -0,0 +1,134 @@
/**
* Copyright 2019 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.v02;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.Collection;
import org.junit.Test;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.extensions.InMemoryFormat;
/**
*
* @author fabiojose
*
*/
public class AccessorTest {
@Test
public void should_empty_collection_when_no_extensions() {
// setup
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withContenttype("text/plain")
.withData("my-data")
.build();
// act
Collection<ExtensionFormat> actual = Accessor.extensionsOf(ce);
// assert
assertTrue(actual.isEmpty());
}
@Test
public void should_return_the_tracing_extension() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat expected = new DistributedTracingExtension.Format(dt);
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withContenttype("text/plain")
.withData("my-data")
.withExtension(expected)
.build();
// act
Collection<ExtensionFormat> extensions =
Accessor.extensionsOf(ce);
// assert
assertFalse(extensions.isEmpty());
ExtensionFormat actual = extensions.iterator().next();
assertEquals("0", actual.transport().get("traceparent"));
assertEquals("congo=4", actual.transport().get("tracestate"));
assertEquals("0",
((DistributedTracingExtension)actual.memory().getValue()).getTraceparent());
assertEquals("congo=4",
((DistributedTracingExtension)actual.memory().getValue()).getTracestate());
}
@Test
public void should_return_the_custom_extension() {
// setup
String customExt = "comexampleextension1";
String customVal = "my-ext-val";
InMemoryFormat inMemory =
InMemoryFormat.of(customExt, customVal, String.class);
ExtensionFormat expected =
ExtensionFormat.of(inMemory, customExt, customVal);
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withContenttype("text/plain")
.withData("my-data")
.withExtension(expected)
.build();
// act
Collection<ExtensionFormat> extensions =
Accessor.extensionsOf(ce);
// assert
assertFalse(extensions.isEmpty());
ExtensionFormat actual = extensions.iterator().next();
assertEquals(customVal, actual.transport().get(customExt));
assertEquals(String.class, actual.memory().getValueType());
assertEquals(customExt, actual.memory().getKey());
assertEquals(customVal, actual.memory().getValue());
}
}

View File

@ -1,6 +1,22 @@
/**
* Copyright 2019 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.v02;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
@ -10,6 +26,12 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.extensions.InMemoryFormat;
import io.cloudevents.json.types.Much;
/**
*
* @author fabiojose
@ -27,7 +49,7 @@ public class CloudEventBuilderTest {
expectedEx.expectMessage("invalid payload: 'id' must not be blank");
// act
new CloudEventBuilder<Object>()
CloudEventBuilder.<Object>builder()
.withSource(URI.create("/test"))
.withType("type")
.build();
@ -40,7 +62,7 @@ public class CloudEventBuilderTest {
expectedEx.expectMessage("invalid payload: 'id' must not be blank");
// act
new CloudEventBuilder<Object>()
CloudEventBuilder.<Object>builder()
.withId("")
.withSource(URI.create("/test"))
.withType("type")
@ -54,7 +76,7 @@ public class CloudEventBuilderTest {
expectedEx.expectMessage("invalid payload: 'type' must not be blank");
// act
new CloudEventBuilder<Object>()
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/test"))
.build();
@ -67,7 +89,7 @@ public class CloudEventBuilderTest {
expectedEx.expectMessage("invalid payload: 'type' must not be blank");
// act
new CloudEventBuilder<Object>()
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/test"))
.withType("")
@ -81,7 +103,7 @@ public class CloudEventBuilderTest {
expectedEx.expectMessage("invalid payload: 'source' must not be null");
// act
new CloudEventBuilder<Object>()
CloudEventBuilder.<Object>builder()
.withId("id")
.withType("type")
.build();
@ -90,57 +112,57 @@ public class CloudEventBuilderTest {
@Test
public void should_have_id() {
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.build();
// assert
assertEquals("id", ce.getId());
assertEquals("id", ce.getAttributes().getId());
}
@Test
public void should_have_source() {
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.build();
// assert
assertEquals(URI.create("/source"), ce.getSource());
assertEquals(URI.create("/source"), ce.getAttributes().getSource());
}
@Test
public void should_have_type() {
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.build();
// assert
assertEquals("type", ce.getType());
assertEquals("type", ce.getAttributes().getType());
}
@Test
public void should_have_specversion() {
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.build();
// assert
assertEquals("0.2", ce.getSpecversion());
assertEquals("0.2", ce.getAttributes().getSpecversion());
}
@Test
@ -149,8 +171,8 @@ public class CloudEventBuilderTest {
ZonedDateTime expected = ZonedDateTime.now();
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
@ -158,8 +180,8 @@ public class CloudEventBuilderTest {
.build();
// assert
assertTrue(ce.getTime().isPresent());
assertEquals(expected, ce.getTime().get());
assertTrue(ce.getAttributes().getTime().isPresent());
assertEquals(expected, ce.getAttributes().getTime().get());
}
@Test
@ -168,8 +190,8 @@ public class CloudEventBuilderTest {
URI expected = URI.create("/schema");
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
@ -177,8 +199,8 @@ public class CloudEventBuilderTest {
.build();
// assert
assertTrue(ce.getSchemaurl().isPresent());
assertEquals(expected, ce.getSchemaurl().get());
assertTrue(ce.getAttributes().getSchemaurl().isPresent());
assertEquals(expected, ce.getAttributes().getSchemaurl().get());
}
@Test
@ -187,8 +209,8 @@ public class CloudEventBuilderTest {
String expected = "application/json";
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
@ -196,8 +218,8 @@ public class CloudEventBuilderTest {
.build();
// assert
assertTrue(ce.getContenttype().isPresent());
assertEquals(expected, ce.getContenttype().get());
assertTrue(ce.getAttributes().getContenttype().isPresent());
assertEquals(expected, ce.getAttributes().getContenttype().get());
}
@Test
@ -206,8 +228,8 @@ public class CloudEventBuilderTest {
String expected = "my data";
// act
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
@ -218,4 +240,82 @@ public class CloudEventBuilderTest {
assertTrue(ce.getData().isPresent());
assertEquals(expected, ce.getData().get());
}
@Test
public void should_have_dte() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
// act
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(tracing)
.build();
Object actual = ce.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY);
// assert
assertNotNull(actual);
assertTrue(actual instanceof DistributedTracingExtension);
}
@Test
public void should_have_custom_extension() {
String myExtKey = "comexampleextension1";
String myExtVal = "value";
ExtensionFormat custom = ExtensionFormat
.of(InMemoryFormat.of(myExtKey, myExtKey, String.class),
myExtKey, myExtVal);
// act
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(custom)
.build();
Object actual = ce.getExtensions()
.get(myExtKey);
assertNotNull(actual);
assertTrue(actual instanceof String);
}
@Test
public void should_builder_change_data_and_id() {
// setup
Much data = new Much();
data.setWow("amzing");
String expected = "amazing";
CloudEventImpl<Much> base =
CloudEventBuilder.<Much>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withData(data)
.build();
// act
CloudEvent<AttributesImpl, String> actual =
CloudEventBuilder.<Much>builder().build(base, "0x010", expected);
// assert
assertTrue(actual.getData().isPresent());
assertEquals(expected, actual.getData().get());
assertEquals("0x010", actual.getAttributes().getId());
}
}

View File

@ -1,3 +1,18 @@
/**
* Copyright 2019 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.v02;
import static org.junit.Assert.assertEquals;
@ -13,6 +28,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.json.Json;
/**
@ -32,8 +48,8 @@ public class CloudEventJacksonTest {
@Test
public void should_encode_right_with_minimal_attrs() {
// setup
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
@ -57,8 +73,8 @@ public class CloudEventJacksonTest {
@Test
public void should_have_optional_attrs() {
// setup
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
@ -84,10 +100,10 @@ public class CloudEventJacksonTest {
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.InMemory(dt);
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
CloudEvent<Object> ce =
new CloudEventBuilder<>()
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
@ -104,60 +120,80 @@ public class CloudEventJacksonTest {
assertTrue(actual.contains(expected));
}
@Test
public void should_not_serialize_attributes_element() {
// setup
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withContenttype("text/plain")
.withData("my-data")
.build();
// act
String actual = Json.encode(ce);
// assert
assertFalse(actual.contains("\"attributes\""));
}
@Test
public void should_have_type() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEventImpl.class);
// assert
assertEquals("aws.s3.object.created", ce.getType());
assertEquals("aws.s3.object.created", ce.getAttributes().getType());
}
@Test
public void should_have_id() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEventImpl.class);
// assert
assertEquals("C234-1234-1234", ce.getId());
assertEquals("C234-1234-1234", ce.getAttributes().getId());
}
//should have time
@Test
public void should_have_time() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEventImpl.class);
// assert
assertTrue(ce.getTime().isPresent());
assertTrue(ce.getAttributes().getTime().isPresent());
}
@Test
public void should_have_source() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEventImpl.class);
// assert
assertEquals(URI.create("https://serverless.com"), ce.getSource());
assertEquals(URI.create("https://serverless.com"), ce.getAttributes().getSource());
}
@Test
public void should_have_contenttype() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEventImpl.class);
// assert
assertTrue(ce.getContenttype().isPresent());
assertEquals("application/json", ce.getContenttype().get());
assertTrue(ce.getAttributes().getContenttype().isPresent());
assertEquals("application/json", ce.getAttributes().getContenttype().get());
}
@Test
public void should_have_specversion() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_new.json"), CloudEventImpl.class);
// assert
assertEquals("0.2", ce.getSpecversion());
assertEquals("0.2", ce.getAttributes().getSpecversion());
}
@Test
@ -167,17 +203,17 @@ public class CloudEventJacksonTest {
expectedEx.expectMessage("invalid payload: 'id' must not be blank");
// act
Json.fromInputStream(resourceOf("02_absent.json"), CloudEvent.class);
Json.fromInputStream(resourceOf("02_absent.json"), CloudEventImpl.class);
}
@Test
public void should_have_tracing_extension() {
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_extension.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_extension.json"), CloudEventImpl.class);
// assert
assertNotNull(ce.getExtensions()
.get(DistributedTracingExtension.InMemory.IN_MEMORY_KEY));
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY));
}
@Test
@ -187,7 +223,7 @@ public class CloudEventJacksonTest {
String expected = "extension-value";
// act
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_extension.json"), CloudEvent.class);
CloudEventImpl<?> ce = Json.fromInputStream(resourceOf("02_extension.json"), CloudEventImpl.class);
// assert
assertEquals(expected, ce.getExtensions()

View File

@ -0,0 +1,127 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author fabiojose
*
*/
public class AttributeMapperTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_headers_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
// act
AttributeMapper.map(null);
}
@Test
public void should_not_map_null_header_value_to_attribute() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce_specversion", null);
// act
Map<String, String> actual = AttributeMapper.map(headers);
// assert
assertFalse(actual.containsKey("specversion"));
}
@Test
public void should_ok_when_no_content_type() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce-specversion", "0.2");
// act
Map<String, String> attributes =
AttributeMapper.map(headers);
// assert
assertFalse(attributes.isEmpty());
}
@Test
public void should_map_cespecversion_to_specversion() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce-specversion", "0.2");
headers.put("Content-Type", "application/json");
String expected = "specversion";
// act
Map<String, String> attributes =
AttributeMapper.map(headers);
// assert
assertNotNull(attributes.get(expected));
}
@Test
public void should_not_map_null_value() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce-type", null);
String expected = "type";
// act
Map<String, String> attributes =
AttributeMapper.map(headers);
// assert
assertFalse(attributes.containsKey(expected));
}
@Test
public void should_all_without_prefix_ce() {
// setup
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
Map<String, String> actual = AttributeMapper.map(myHeaders);
actual.keySet()
.forEach((attribute) -> {
assertFalse(attribute.startsWith("ce-"));
});
}
}

View File

@ -0,0 +1,103 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author fabiojose
*
*/
public class ExtensionMapperTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_headers_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
// act
ExtensionMapper.map(null);
}
@Test
public void should_not_map_null_values() {
//setuṕ
String expected = "nullexp";
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("my-ext", "myextension");
myHeaders.put("traceparent", "0");
myHeaders.put("tracestate", "congo=4");
myHeaders.put("Content-Type", "application/json");
myHeaders.put(expected, null);
// act
Map<String, String> actual = ExtensionMapper.map(myHeaders);
// assert
assertFalse(actual.containsKey(expected));
}
@Test
public void should_return_just_potential_extensions() {
// setup
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("my-ext", "myextension");
myHeaders.put("traceparent", "0");
myHeaders.put("tracestate", "congo=4");
myHeaders.put("Content-Type", "application/json");
// act
Map<String, String> actual = ExtensionMapper.map(myHeaders);
// asset
assertFalse(actual.isEmpty());
assertEquals(3, actual.keySet().size());
actual.keySet()
.forEach(header -> {
assertFalse(header.startsWith("ce-"));
});
assertEquals("0", actual.get("traceparent"));
assertEquals("congo=4", actual.get("tracestate"));
assertEquals("myextension", actual.get("my-ext"));
}
}

View File

@ -0,0 +1,130 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import org.junit.Test;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.Wire;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPBinaryMarshallerTest {
@Test
public void should_marshal_data_as_json() {
// setup
String expected = "{\"wow\":\"yes!\"}";
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> ce =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("application/json")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<Much>
binary()
.withEvent(() -> ce)
.marshal();
// assert
assertTrue(actual.getPayload().isPresent());
assertEquals(expected, actual.getPayload().get());
}
@Test
public void should_marshal_attributes_as_headers() {
// setup
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> ce =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("application/json")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<Much>
binary()
.withEvent(() -> ce)
.marshal();
// assert
assertFalse(actual.getHeaders().isEmpty());
assertEquals(ce.getAttributes().getId(), actual.getHeaders().get("ce-id"));
assertEquals(ce.getAttributes().getSource(), URI.create(actual.getHeaders().get("ce-source")));
assertEquals(ce.getAttributes().getType(), actual.getHeaders().get("ce-type"));
assertEquals(ce.getAttributes().getContenttype().get(), actual.getHeaders().get("Content-Type"));
}
@Test
public void should_marshal_the_tracing_extension_as_header() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(tracing)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>
binary()
.withEvent(() -> ce)
.marshal();
assertFalse(actual.getHeaders().isEmpty());
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_PARENT_KEY));
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_STATE_KEY));
}
}

View File

@ -0,0 +1,112 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.AttributesImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPBinaryUnmarshallerTest {
@Test
public void should_unmarshal_headers_and_json_payload() {
// setup
Much expected = new Much();
expected.setWow("yes!");
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
String payload = "{\"wow\":\"yes!\"}";
// act
CloudEvent<AttributesImpl, Much> actual =
Unmarshallers.binary(Much.class)
.withHeaders(() -> myHeaders)
.withPayload(() -> payload)
.unmarshal();
// assert
assertEquals("0x11", actual.getAttributes().getId());
assertEquals(URI.create("/source"), actual.getAttributes().getSource());
assertEquals("0.2", actual.getAttributes().getSpecversion());
assertEquals("br.my", actual.getAttributes().getType());
assertTrue(actual.getAttributes().getTime().isPresent());
assertTrue(actual.getAttributes().getSchemaurl().isPresent());
assertEquals(URI.create("http://my.br"), actual.getAttributes().getSchemaurl().get());
assertTrue(actual.getAttributes().getContenttype().isPresent());
assertEquals("application/json", actual.getAttributes().getContenttype().get());
assertTrue(actual.getData().isPresent());
assertEquals(expected, actual.getData().get());
}
@Test
public void should_unmarshal_tracing_extension_from_header() {
// setup
Much expected = new Much();
expected.setWow("yes!");
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
myHeaders.put("traceparent", "0x200");
myHeaders.put("tracestate", "congo=9");
String payload = "{\"wow\":\"yes!\"}";
// act
CloudEvent<AttributesImpl, Much> actual =
Unmarshallers.binary(Much.class)
.withHeaders(() -> myHeaders)
.withPayload(() -> payload)
.unmarshal();
// assert
assertNotNull(actual.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY));
assertTrue(actual.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY)
instanceof DistributedTracingExtension);
}
}

View File

@ -0,0 +1,149 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import org.junit.Test;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.Wire;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPStructuredMarshallerTest {
@Test
public void should_marshal_all_as_json() {
// setup
String expected = "{\"data\":{\"wow\":\"yes!\"},\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"application/json\"}";
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> ce =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("application/json")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<Much>structured()
.withEvent(() -> ce)
.marshal();
assertTrue(actual.getPayload().isPresent());
assertEquals(expected, actual.getPayload().get());
}
@Test
public void should_marshal_data_as_text_and_evelope_as_json() {
// setup
String expected = "{\"data\":\"yes!\",\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"text/plain\"}";
String ceData = "yes!";
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("text/plain")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
assertTrue(actual.getPayload().isPresent());
assertEquals(expected, actual.getPayload().get());
}
@Test
public void should_headers_have_content_type() {
// setup
String expected = "application/cloudevents+json";
String ceData = "yes!";
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("text/plain")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
assertFalse(actual.getHeaders().isEmpty());
assertTrue(actual.getHeaders().containsKey("Content-Type"));
assertEquals(expected, actual.getHeaders().get("Content-Type"));
}
@Test
public void should_marshal_the_tracing_extension_as_header() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(tracing)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
// assert
assertFalse(actual.getHeaders().isEmpty());
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_PARENT_KEY));
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_STATE_KEY));
}
}

View File

@ -0,0 +1,153 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.json.types.Much;
import io.cloudevents.v02.AttributesImpl;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPStructuredUnmasharllerTest {
@Test
public void should_unmarshal_json_envelope_and_json_data() {
// setup
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
String json = "{\"data\":{\"wow\":\"yes!\"},\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"application/json\"}";
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> expected =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("application/json")
.withData(ceData)
.build();
// act
CloudEvent<AttributesImpl, Much> actual =
Unmarshallers.structured(Much.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> json)
.unmarshal();
// assert
assertEquals(expected.getAttributes().getSpecversion(),
actual.getAttributes().getSpecversion());
assertEquals(expected.getAttributes().getId(),
actual.getAttributes().getId());
assertEquals(expected.getAttributes().getSource(),
actual.getAttributes().getSource());
assertEquals(expected.getAttributes().getType(),
actual.getAttributes().getType());
assertTrue(actual.getData().isPresent());
assertEquals(expected.getData().get(), actual.getData().get());
}
@Test
public void should_unmarshal_json_envelope_and_text_data() {
// setup
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
String json = "{\"data\":\"yes!\",\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"text/plain\"}";
String ceData = "yes!";
CloudEventImpl<String> expected =
CloudEventBuilder.<String>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withContenttype("text/plain")
.withData(ceData)
.build();
// act
CloudEvent<AttributesImpl, String> actual =
Unmarshallers.structured(String.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> json)
.unmarshal();
// assert
assertEquals(expected.getAttributes().getSpecversion(),
actual.getAttributes().getSpecversion());
assertEquals(expected.getAttributes().getId(),
actual.getAttributes().getId());
assertEquals(expected.getAttributes().getSource(),
actual.getAttributes().getSource());
assertEquals(expected.getAttributes().getType(),
actual.getAttributes().getType());
assertTrue(actual.getData().isPresent());
assertEquals(expected.getData().get(), actual.getData().get());
}
@Test
public void should_unmarshal_the_tracing_extension_from_headers() {
// setup
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
httpHeaders.put("traceparent", "0x200");
httpHeaders.put("tracestate", "congo=9");
String json = "{\"data\":\"yes!\",\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"contenttype\":\"text/plain\"}";
// act
CloudEvent<AttributesImpl, String> actual =
Unmarshallers.structured(String.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> json)
.unmarshal();
// assert
assertTrue(actual.getExtensions().containsKey(
DistributedTracingExtension.Format.IN_MEMORY_KEY));
assertTrue(actual.getExtensions().get(
DistributedTracingExtension.Format.IN_MEMORY_KEY)
instanceof DistributedTracingExtension);
}
}

View File

@ -0,0 +1,129 @@
/**
* Copyright 2019 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.v02.http;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author fabiojose
*
*/
public class HeaderMapperTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_attributes_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
Map<String, String> extensions = new HashMap<>();
// act
HeaderMapper.map(null, extensions);
}
@Test
public void error_when_extensions_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
Map<String, String> attributes = new HashMap<>();
// act
HeaderMapper.map(attributes, null);
}
@Test
public void should_not_map_null_attribute_value() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", null);
attributes.put("specversion", "0.2");
Map<String, String> extensions = new HashMap<>();
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertFalse(actual.containsKey("ce-type"));
}
@Test
public void should_not_map_null_extension_value() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", "mytype");
attributes.put("specversion", "0.2");
Map<String, String> extensions = new HashMap<>();
extensions.put("null-ext", null);
extensions.put("comexampleextension1", "value");
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertFalse(actual.containsKey("null-ext"));
}
@Test
public void should_not_map_absent_contenttype() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", "mytype");
attributes.put("specversion", "0.2");
Map<String, String> extensions = new HashMap<>();
extensions.put("null-ext", "null-value");
extensions.put("comexampleextension1", "value");
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertFalse(actual.containsKey("Content-Type"));
}
@Test
public void should_map_extension_without_prefix() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", "mytype");
attributes.put("specversion", "0.2");
Map<String, String> extensions = new HashMap<>();
extensions.put("null-ext", "null-value");
extensions.put("comexampleextension1", "value");
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertTrue(actual.containsKey("comexampleextension1"));
}
}

View File

@ -0,0 +1,137 @@
/**
* Copyright 2019 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.v03;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.Collection;
import org.junit.Test;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.extensions.InMemoryFormat;
import io.cloudevents.v03.Accessor;
import io.cloudevents.v03.CloudEventBuilder;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class AccessorTest {
@Test
public void should_empty_collection_when_no_extensions() {
// setup
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withDatacontenttype("text/plain")
.withData("my-data")
.build();
// act
Collection<ExtensionFormat> actual = Accessor.extensionsOf(ce);
// assert
assertTrue(actual.isEmpty());
}
@Test
public void should_return_the_tracing_extension() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat expected = new DistributedTracingExtension.Format(dt);
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withDatacontenttype("text/plain")
.withData("my-data")
.withExtension(expected)
.build();
// act
Collection<ExtensionFormat> extensions =
Accessor.extensionsOf(ce);
// assert
assertFalse(extensions.isEmpty());
ExtensionFormat actual = extensions.iterator().next();
assertEquals("0", actual.transport().get("traceparent"));
assertEquals("congo=4", actual.transport().get("tracestate"));
assertEquals("0",
((DistributedTracingExtension)actual.memory().getValue()).getTraceparent());
assertEquals("congo=4",
((DistributedTracingExtension)actual.memory().getValue()).getTracestate());
}
@Test
public void should_return_the_custom_extension() {
// setup
String customExt = "comexampleextension1";
String customVal = "my-ext-val";
InMemoryFormat inMemory =
InMemoryFormat.of(customExt, customVal, String.class);
ExtensionFormat expected =
ExtensionFormat.of(inMemory, customExt, customVal);
CloudEventImpl<Object> ce =
CloudEventBuilder.<Object>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withDatacontenttype("text/plain")
.withData("my-data")
.withExtension(expected)
.build();
// act
Collection<ExtensionFormat> extensions =
Accessor.extensionsOf(ce);
// assert
assertFalse(extensions.isEmpty());
ExtensionFormat actual = extensions.iterator().next();
assertEquals(customVal, actual.transport().get(customExt));
assertEquals(String.class, actual.memory().getValueType());
assertEquals(customExt, actual.memory().getKey());
assertEquals(customVal, actual.memory().getValue());
}
}

View File

@ -0,0 +1,207 @@
/**
* Copyright 2019 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.v03;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.extensions.InMemoryFormat;
/**
*
* @author fabiojose
*
*/
public class CloudEventBuilderTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_null_id() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'id' must not be blank");
// act
CloudEventBuilder.builder()
.withSource(URI.create("/test"))
.withType("type")
.build();
}
@Test
public void error_when_empty_id() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'id' must not be blank");
// act
CloudEventBuilder.builder()
.withId("")
.withSource(URI.create("/test"))
.withType("type")
.build();
}
@Test
public void error_when_null_type() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'type' must not be blank");
// act
CloudEventBuilder.builder()
.withId("id")
.withSource(URI.create("/test"))
.build();
}
@Test
public void error_when_empty_type() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'type' must not be blank");
// act
CloudEventBuilder.builder()
.withId("id")
.withSource(URI.create("/test"))
.withType("")
.build();
}
@Test
public void error_when_null_source() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'source' must not be null");
// act
CloudEventBuilder.builder()
.withId("id")
.withType("type")
.build();
}
@Test
public void error_when_empty_subject() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'subject' size must be between 1 and 2147483647");
// act
CloudEventBuilder.<Object>builder()
.withId("id")
.withType("type")
.withSource(URI.create("/source"))
.withSubject("")
.build();
}
@Test
public void error_when_invalid_encoding() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'datacontentencoding' must match \"base64\"");
// act
CloudEventBuilder.<Object>builder()
.withId("id")
.withType("type")
.withSource(URI.create("/source"))
.withSubject("subject")
.withDatacontentencoding("binary")
.build();
}
@Test
public void should_have_subject() {
// act
CloudEvent<AttributesImpl, Object> ce =
CloudEventBuilder.<Object>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withSubject("subject")
.build();
// assert
assertTrue(ce.getAttributes().getSubject().isPresent());
assertEquals("subject", ce.getAttributes().getSubject().get());
}
@Test
public void should_have_dte() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
// act
CloudEventImpl<Object> ce =
CloudEventBuilder.builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(tracing)
.build();
Object actual = ce.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY);
// assert
assertNotNull(actual);
assertTrue(actual instanceof DistributedTracingExtension);
}
@Test
public void should_have_custom_extension() {
String myExtKey = "comexampleextension1";
String myExtVal = "value";
ExtensionFormat custom = ExtensionFormat
.of(InMemoryFormat.of(myExtKey, myExtKey, String.class),
myExtKey, myExtVal);
// act
CloudEventImpl<Object> ce =
CloudEventBuilder.builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(custom)
.build();
Object actual = ce.getExtensions()
.get(myExtKey);
assertNotNull(actual);
assertTrue(actual instanceof String);
}
}

View File

@ -0,0 +1,283 @@
/**
* Copyright 2019 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.v03;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.InputStream;
import java.net.URI;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import com.fasterxml.jackson.core.type.TypeReference;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.json.Json;
import io.cloudevents.json.types.Much;
/**
*
* @author fabiojose
*
*/
public class CloudEventJacksonTest {
private static InputStream resourceOf(String name) {
return Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
}
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void should_encode_right_with_minimal_attrs() {
// setup
CloudEvent<AttributesImpl, Object> ce =
CloudEventBuilder.builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.build();
// act
String json = Json.encode(ce);
// assert
assertTrue(json.contains("x10"));
assertTrue(json.contains("/source"));
assertTrue(json.contains("event-type"));
assertTrue(json.contains("0.3"));
assertFalse(json.contains("time"));
assertFalse(json.contains("schemaurl"));
assertFalse(json.contains("contenttype"));
assertFalse(json.contains("data"));
}
@Test
public void should_have_optional_attrs() {
// setup
CloudEvent<AttributesImpl, Object> ce =
CloudEventBuilder.builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withDatacontenttype("text/plain")
.withDatacontentencoding("base64")
.withSubject("subject0")
.withData("my-data")
.build();
// act
String json = Json.encode(ce);
// assert
assertTrue(json.contains("/schema"));
assertTrue(json.contains("text/plain"));
assertTrue(json.contains("my-data"));
assertTrue(json.contains("\"base64\""));
assertTrue(json.contains("subject0"));
assertTrue(json.contains("\"schemaurl\""));
assertTrue(json.contains("datacontenttype"));
assertTrue(json.contains("datacontentencoding"));
assertTrue(json.contains("\"subject\""));
}
@Test
public void should_serialize_trace_extension() {
// setup
String expected = "\"distributedTracing\":{\"traceparent\":\"0\",\"tracestate\":\"congo=4\"}";
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
CloudEvent<AttributesImpl, Object> ce =
CloudEventBuilder.builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withDatacontenttype("text/plain")
.withData("my-data")
.withExtension(tracing)
.build();
// act
String actual = Json.encode(ce);
// assert
assertTrue(actual.contains(expected));
}
@Test
public void should_not_serialize_attributes_element() {
// setup
CloudEvent<AttributesImpl, Object> ce =
CloudEventBuilder.builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withSchemaurl(URI.create("/schema"))
.withDatacontenttype("text/plain")
.withSubject("subject0")
.withData("my-data")
.build();
// act
String actual = Json.encode(ce);
// assert
assertFalse(actual.contains("\"attributes\""));
}
@Test
public void should_have_type() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_new.json"), CloudEventImpl.class);
// assert
assertEquals("aws.s3.object.created", ce.getAttributes().getType());
}
@Test
public void should_have_id() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_new.json"), CloudEventImpl.class);
// assert
assertEquals("C234-1234-1234", ce.getAttributes().getId());
}
//should have time
@Test
public void should_have_time() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_new.json"), CloudEventImpl.class);
// assert
assertTrue(ce.getAttributes().getTime().isPresent());
}
@Test
public void should_have_source() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_new.json"), CloudEventImpl.class);
// assert
assertEquals(URI.create("https://serverless.com"), ce.getAttributes().getSource());
}
@Test
public void should_have_datacontenttype() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_new.json"), CloudEventImpl.class);
// assert
assertTrue(ce.getAttributes().getDatacontenttype().isPresent());
assertEquals("application/json", ce.getAttributes().getDatacontenttype().get());
}
@Test
public void should_have_datacontentencoding() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_base64.json"), CloudEventImpl.class);
// assert
assertTrue(ce.getAttributes().getDatacontentencoding().isPresent());
assertEquals("base64", ce.getAttributes().getDatacontentencoding().get());
}
@Test
public void should_have_specversion() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_new.json"), CloudEventImpl.class);
// assert
assertEquals("0.3", ce.getAttributes().getSpecversion());
}
@Test
public void should_throw_when_absent() {
// setup
expectedEx.expect(IllegalStateException.class);
expectedEx.expectMessage("invalid payload: 'id' must not be blank");
// act
Json.fromInputStream(resourceOf("03_absent.json"), CloudEventImpl.class);
}
@Test
public void should_have_tracing_extension() {
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_extension.json"), CloudEventImpl.class);
// assert
assertNotNull(ce.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY));
}
@Test
public void should_have_custom_extension() {
// setup
String extensionKey = "my-extension";
String expected = "extension-value";
// act
CloudEvent<AttributesImpl, Object> ce =
Json.fromInputStream(resourceOf("03_extension.json"), CloudEventImpl.class);
// assert
assertEquals(expected, ce.getExtensions()
.get(extensionKey));
}
@Test
public void should_have_custom_data() {
// setup
Much expected = new Much();
expected.setWow("kinda");
String json = "{\"type\":\"aws.s3.object.created\",\"id\":\"C234-1234-1234\",\"time\":\"2019-08-19T19:35:00.000Z\",\"source\":\"https://serverless.com\",\"datacontenttype\":\"application/json\",\"specversion\":\"0.3\",\"data\":{\"wow\":\"kinda\"}}";
// act
CloudEvent<AttributesImpl, Much> ce =
Json.decodeValue(json, new TypeReference<CloudEventImpl<Much>>() {});
// assert
assertTrue(ce.getData().isPresent());
assertEquals(expected.getWow(), ce.getData().get().getWow());
}
}

View File

@ -0,0 +1,114 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.v03.http.AttributeMapper;
/**
*
* @author fabiojose
*
*/
public class AttributeMapperTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_headers_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
// act
AttributeMapper.map(null);
}
@Test
public void should_not_map_null_value() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce-type", null);
String expected = "type";
// act
Map<String, String> attributes =
AttributeMapper.map(headers);
// assert
assertFalse(attributes.containsKey(expected));
}
@Test
public void should_ok_when_no_content_type() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce-specversion", "0.3");
// act
Map<String, String> attributes =
AttributeMapper.map(headers);
// assert
assertFalse(attributes.isEmpty());
}
@Test
public void should_map_cespecversion_to_specversion() {
// setup
Map<String, Object> headers = new HashMap<>();
headers.put("ce-specversion", "0.3");
headers.put("Content-Type", "application/json");
String expected = "specversion";
// act
Map<String, String> attributes =
AttributeMapper.map(headers);
// assert
assertNotNull(attributes.get(expected));
}
@Test
public void should_all_without_prefix_ce() {
// setup
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.3");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
Map<String, String> actual = AttributeMapper.map(myHeaders);
actual.keySet()
.forEach((attribute) -> {
assertFalse(attribute.startsWith("ce-"));
});
}
}

View File

@ -0,0 +1,105 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import io.cloudevents.v03.http.ExtensionMapper;
/**
*
* @author fabiojose
*
*/
public class ExtensionMapperTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_headers_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
// act
ExtensionMapper.map(null);
}
@Test
public void should_not_map_null_values() {
//setuṕ
String expected = "nullexp";
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.3");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("my-ext", "myextension");
myHeaders.put("traceparent", "0");
myHeaders.put("tracestate", "congo=4");
myHeaders.put("Content-Type", "application/json");
myHeaders.put(expected, null);
// act
Map<String, String> actual = ExtensionMapper.map(myHeaders);
// assert
assertFalse(actual.containsKey(expected));
}
@Test
public void should_return_just_potential_extensions() {
// setup
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.3");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("my-ext", "myextension");
myHeaders.put("traceparent", "0");
myHeaders.put("tracestate", "congo=4");
myHeaders.put("Content-Type", "application/json");
// act
Map<String, String> actual = ExtensionMapper.map(myHeaders);
// asset
assertFalse(actual.isEmpty());
assertEquals(3, actual.keySet().size());
actual.keySet()
.forEach(header -> {
assertFalse(header.startsWith("ce-"));
});
assertEquals("0", actual.get("traceparent"));
assertEquals("congo=4", actual.get("tracestate"));
assertEquals("myextension", actual.get("my-ext"));
}
}

View File

@ -0,0 +1,131 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import org.junit.Test;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.Wire;
import io.cloudevents.json.types.Much;
import io.cloudevents.v03.CloudEventBuilder;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPBinaryMarshallerTest {
@Test
public void should_marshal_data_as_json() {
// setup
String expected = "{\"wow\":\"yes!\"}";
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> ce =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("application/json")
.withSubject("subject")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<Much>
binary()
.withEvent(() -> ce)
.marshal();
// assert
assertTrue(actual.getPayload().isPresent());
assertEquals(expected, actual.getPayload().get());
}
@Test
public void should_marshal_attributes_as_headers() {
// setup
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> ce =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("application/json")
.withSubject("subject")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<Much>
binary()
.withEvent(() -> ce)
.marshal();
// assert
assertFalse(actual.getHeaders().isEmpty());
assertEquals(ce.getAttributes().getId(), actual.getHeaders().get("ce-id"));
assertEquals(ce.getAttributes().getSource(), URI.create(actual.getHeaders().get("ce-source")));
assertEquals(ce.getAttributes().getType(), actual.getHeaders().get("ce-type"));
assertEquals(ce.getAttributes().getSubject().get(), actual.getHeaders().get("ce-subject"));
assertEquals(ce.getAttributes().getDatacontenttype().get(), actual.getHeaders().get("Content-Type"));
}
@Test
public void should_marshal_the_tracing_extension_as_header() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(tracing)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>
binary()
.withEvent(() -> ce)
.marshal();
assertFalse(actual.getHeaders().isEmpty());
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_PARENT_KEY));
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_STATE_KEY));
}
}

View File

@ -0,0 +1,114 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.json.types.Much;
import io.cloudevents.v03.AttributesImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPBinaryUnmarshallerTest {
@Test
public void should_unmarshal_headers_and_json_payload() {
// setup
Much expected = new Much();
expected.setWow("yes!");
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("ce-subject", "subject");
myHeaders.put("Content-Type", "application/json");
String payload = "{\"wow\":\"yes!\"}";
// act
CloudEvent<AttributesImpl, Much> actual =
Unmarshallers.binary(Much.class)
.withHeaders(() -> myHeaders)
.withPayload(() -> payload)
.unmarshal();
// assert
assertEquals("0x11", actual.getAttributes().getId());
assertEquals(URI.create("/source"), actual.getAttributes().getSource());
assertEquals("0.3", actual.getAttributes().getSpecversion());
assertEquals("br.my", actual.getAttributes().getType());
assertTrue(actual.getAttributes().getTime().isPresent());
assertTrue(actual.getAttributes().getSchemaurl().isPresent());
assertEquals(URI.create("http://my.br"), actual.getAttributes().getSchemaurl().get());
assertTrue(actual.getAttributes().getDatacontenttype().isPresent());
assertEquals("application/json", actual.getAttributes().getDatacontenttype().get());
assertTrue(actual.getData().isPresent());
assertEquals(expected, actual.getData().get());
assertTrue(actual.getAttributes().getSubject().isPresent());
assertEquals("subject", actual.getAttributes().getSubject().get());
}
@Test
public void should_unmarshal_tracing_extension_from_header() {
// setup
Much expected = new Much();
expected.setWow("yes!");
Map<String, Object> myHeaders = new HashMap<>();
myHeaders.put("ce-id", "0x11");
myHeaders.put("ce-source", "/source");
myHeaders.put("ce-specversion", "0.2");
myHeaders.put("ce-type", "br.my");
myHeaders.put("ce-time", "2019-09-16T20:49:00Z");
myHeaders.put("ce-schemaurl", "http://my.br");
myHeaders.put("Content-Type", "application/json");
myHeaders.put("traceparent", "0x200");
myHeaders.put("tracestate", "congo=9");
String payload = "{\"wow\":\"yes!\"}";
// act
CloudEvent<AttributesImpl, Much> actual =
Unmarshallers.binary(Much.class)
.withHeaders(() -> myHeaders)
.withPayload(() -> payload)
.unmarshal();
// assert
assertNotNull(actual.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY));
assertTrue(actual.getExtensions()
.get(DistributedTracingExtension.Format.IN_MEMORY_KEY)
instanceof DistributedTracingExtension);
}
}

View File

@ -0,0 +1,149 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import org.junit.Test;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.extensions.ExtensionFormat;
import io.cloudevents.format.Wire;
import io.cloudevents.json.types.Much;
import io.cloudevents.v03.CloudEventBuilder;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPStructuredMarshallerTest {
@Test
public void should_marshal_all_as_json() {
// setup
String expected = "{\"data\":{\"wow\":\"yes!\"},\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.3\",\"type\":\"event-type\",\"datacontenttype\":\"application/json\",\"subject\":\"subject\"}";
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> ce =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("application/json")
.withSubject("subject")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<Much>structured()
.withEvent(() -> ce)
.marshal();
assertTrue(actual.getPayload().isPresent());
assertEquals(expected, actual.getPayload().get());
}
@Test
public void should_marshal_data_as_text_and_evelope_as_json() {
// setup
String expected = "{\"data\":\"yes!\",\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.3\",\"type\":\"event-type\",\"datacontenttype\":\"text/plain\"}";
String ceData = "yes!";
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("text/plain")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
assertTrue(actual.getPayload().isPresent());
assertEquals(expected, actual.getPayload().get());
}
@Test
public void should_headers_have_content_type() {
// setup
String expected = "application/cloudevents+json";
String ceData = "yes!";
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("text/plain")
.withData(ceData)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
assertFalse(actual.getHeaders().isEmpty());
assertTrue(actual.getHeaders().containsKey("Content-Type"));
assertEquals(expected, actual.getHeaders().get("Content-Type"));
}
@Test
public void should_marshal_the_tracing_extension_as_header() {
// setup
final DistributedTracingExtension dt = new DistributedTracingExtension();
dt.setTraceparent("0");
dt.setTracestate("congo=4");
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
CloudEventImpl<String> ce =
CloudEventBuilder.<String>builder()
.withId("id")
.withSource(URI.create("/source"))
.withType("type")
.withExtension(tracing)
.build();
// act
Wire<String, String, String> actual =
Marshallers.<String>structured()
.withEvent(() -> ce)
.marshal();
// assert
assertFalse(actual.getHeaders().isEmpty());
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_PARENT_KEY));
assertNotNull(actual.getHeaders().get(DistributedTracingExtension
.Format.TRACE_STATE_KEY));
}
}

View File

@ -0,0 +1,157 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import io.cloudevents.CloudEvent;
import io.cloudevents.extensions.DistributedTracingExtension;
import io.cloudevents.json.types.Much;
import io.cloudevents.v03.AttributesImpl;
import io.cloudevents.v03.CloudEventBuilder;
import io.cloudevents.v03.CloudEventImpl;
/**
*
* @author fabiojose
*
*/
public class HTTPStructuredUnmarshaller {
@Test
public void should_unmarshal_json_envelope_and_json_data() {
// setup
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
String json = "{\"data\":{\"wow\":\"yes!\"},\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.2\",\"type\":\"event-type\",\"datacontenttype\":\"application/json\",\"subject\":\"subject\"}";
Much ceData = new Much();
ceData.setWow("yes!");
CloudEventImpl<Much> expected =
CloudEventBuilder.<Much>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("application/json")
.withSubject("subject")
.withData(ceData)
.build();
// act
CloudEvent<AttributesImpl, Much> actual =
Unmarshallers.structured(Much.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> json)
.unmarshal();
// assert
assertEquals(expected.getAttributes().getSpecversion(),
actual.getAttributes().getSpecversion());
assertEquals(expected.getAttributes().getId(),
actual.getAttributes().getId());
assertEquals(expected.getAttributes().getSource(),
actual.getAttributes().getSource());
assertEquals(expected.getAttributes().getType(),
actual.getAttributes().getType());
assertTrue(actual.getData().isPresent());
assertEquals(expected.getData().get(), actual.getData().get());
assertTrue(actual.getAttributes().getSubject().isPresent());
assertEquals(expected.getAttributes().getSubject().get(),
actual.getAttributes().getSubject().get());
}
@Test
public void should_unmarshal_json_envelope_and_text_data() {
// setup
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
String json = "{\"data\":\"yes!\",\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.3\",\"type\":\"event-type\",\"datacontenttype\":\"text/plain\"}";
String ceData = "yes!";
CloudEventImpl<String> expected =
CloudEventBuilder.<String>builder()
.withId("x10")
.withSource(URI.create("/source"))
.withType("event-type")
.withDatacontenttype("text/plain")
.withData(ceData)
.build();
// act
CloudEvent<AttributesImpl, String> actual =
Unmarshallers.structured(String.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> json)
.unmarshal();
// assert
assertEquals(expected.getAttributes().getSpecversion(),
actual.getAttributes().getSpecversion());
assertEquals(expected.getAttributes().getId(),
actual.getAttributes().getId());
assertEquals(expected.getAttributes().getSource(),
actual.getAttributes().getSource());
assertEquals(expected.getAttributes().getType(),
actual.getAttributes().getType());
assertTrue(actual.getData().isPresent());
assertEquals(expected.getData().get(), actual.getData().get());
}
@Test
public void should_unmarshal_the_tracing_extension_from_headers() {
// setup
Map<String, Object> httpHeaders = new HashMap<>();
httpHeaders.put("Content-Type", "application/cloudevents+json");
httpHeaders.put("traceparent", "0x200");
httpHeaders.put("tracestate", "congo=9");
String json = "{\"data\":\"yes!\",\"id\":\"x10\",\"source\":\"/source\",\"specversion\":\"0.3\",\"type\":\"event-type\",\"datacontenttype\":\"text/plain\"}";
// act
CloudEvent<AttributesImpl, String> actual =
Unmarshallers.structured(String.class)
.withHeaders(() -> httpHeaders)
.withPayload(() -> json)
.unmarshal();
// assert
assertTrue(actual.getExtensions().containsKey(
DistributedTracingExtension.Format.IN_MEMORY_KEY));
assertTrue(actual.getExtensions().get(
DistributedTracingExtension.Format.IN_MEMORY_KEY)
instanceof DistributedTracingExtension);
}
}

View File

@ -0,0 +1,109 @@
/**
* Copyright 2019 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.v03.http;
import static org.junit.Assert.assertFalse;
import java.util.HashMap;
import java.util.Map;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
/**
*
* @author fabiojose
*
*/
public class HeaderMapperTest {
@Rule
public ExpectedException expectedEx = ExpectedException.none();
@Test
public void error_when_attributes_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
Map<String, String> extensions = new HashMap<>();
// act
HeaderMapper.map(null, extensions);
}
@Test
public void error_when_extensions_map_isnull() {
// setup
expectedEx.expect(NullPointerException.class);
Map<String, String> attributes = new HashMap<>();
// act
HeaderMapper.map(attributes, null);
}
@Test
public void should_not_map_null_attribute_value() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", null);
attributes.put("specversion", "0.3");
Map<String, String> extensions = new HashMap<>();
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertFalse(actual.containsKey("ce-type"));
}
@Test
public void should_not_map_null_extension_value() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", "mytype");
attributes.put("specversion", "0.2");
Map<String, String> extensions = new HashMap<>();
extensions.put("null-ext", null);
extensions.put("comexampleextension1", "value");
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertFalse(actual.containsKey("ce-null-ext"));
}
@Test
public void should_not_map_absent_datacontenttype() {
// setup
Map<String, String> attributes = new HashMap<>();
attributes.put("type", "mytype");
attributes.put("specversion", "0.2");
Map<String, String> extensions = new HashMap<>();
extensions.put("null-ext", "null-value");
extensions.put("comexampleextension1", "value");
// act
Map<String, String> actual = HeaderMapper.map(attributes, extensions);
//assert
assertFalse(actual.containsKey("Content-Type"));
}
}

View File

@ -0,0 +1,7 @@
{
"type": "aws.s3.object.created",
"time": "2018-04-26T14:48:09.769Z",
"source": "https://serverless.com",
"datacontenttype": "application/json",
"specversion": "0.3"
}

View File

@ -0,0 +1,11 @@
{
"type": "aws.s3.object.created",
"id": "C234-1234-1234",
"time": "2018-04-26T14:48:09.769Z",
"source": "https://serverless.com",
"datacontenttype": "application/json",
"datacontentencoding": "base64",
"specversion": "0.3",
"data":"eyJzM1NjaGVtYVZlcnNpb24iOiIxLjAiLCJjb25maWd1cmF0aW9uSWQiOiJjZDI2N2EzOC0zMGRmLTQwMGUtOWUzZC1kMGYxY2E2ZTI0MTAiLCJidWNrZXQiOnsibmFtZSI6ImNsb3VkZXZlbnRzIiwib3duZXJJZGVudGl0eSI6e30sImFybiI6ImFybjphd3M6czM6OjpjbG91ZGV2ZW50cyJ9LCJvYmplY3QiOnsia2V5IjoiZGFuX2tvaG4uanBnIiwic2l6ZSI6NDQ0Njg0LCJlVGFnIjoiMzhiMDFmZjE2MTM4ZDdjYTBhMGViM2Y3YTg4ZmY4MTUiLCJzZXF1ZW5jZXIiOiIwMDVBRTFFNkE5QTNENjE0OTAifX0K",
"my-extension" : "extension-value"
}

View File

@ -0,0 +1,27 @@
{
"type": "aws.s3.object.created",
"id": "C234-1234-1234",
"time": "2018-04-26T14:48:09.769Z",
"source": "https://serverless.com",
"datacontenttype": "application/json",
"specversion": "0.3",
"data":
{ "s3SchemaVersion": "1.0",
"configurationId": "cd267a38-30df-400e-9e3d-d0f1ca6e2410",
"bucket":
{ "name": "cloudevents",
"ownerIdentity": {},
"arn": "arn:aws:s3:::cloudevents" },
"object":
{ "key": "dan_kohn.jpg",
"size": 444684,
"eTag": "38b01ff16138d7ca0a0eb3f7a88ff815",
"sequencer": "005AE1E6A9A3D61490"
}
},
"my-extension" : "extension-value",
"distributedTracing": {
"traceparent": "0",
"tracestate": "congo=4"
}
}

View File

@ -0,0 +1,23 @@
{
"type": "aws.s3.object.created",
"id": "C234-1234-1234",
"time": "2018-04-26T14:48:09.769Z",
"source": "https://serverless.com",
"datacontenttype": "application/json",
"specversion": "0.3",
"data":
{ "s3SchemaVersion": "1.0",
"configurationId": "cd267a38-30df-400e-9e3d-d0f1ca6e2410",
"bucket":
{ "name": "cloudevents",
"ownerIdentity": {},
"arn": "arn:aws:s3:::cloudevents" },
"object":
{ "key": "dan_kohn.jpg",
"size": 444684,
"eTag": "38b01ff16138d7ca0a0eb3f7a88ff815",
"sequencer": "005AE1E6A9A3D61490"
}
},
"my-extension" : "extension-value"
}

View File

@ -8,15 +8,15 @@ For Maven based projects, use the following to configure the CloudEvents CDI lib
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cdi</artifactId>
<version>0.2.0</version>
<version>0.3.0</version>
</dependency>
```
In _Enterprise Java_ applications, implemented with [Jakarta EE](https://jakarta.ee/) or the [Eclipse MicroProfile](https://microprofile.io/), it's trivial to combine this CloudEvents API with CDI. Application developers can now fire a CloudEvent for further processing inside of the application:
```java
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventBuilder;
import io.cloudevents.v02.CloudEventBuilder;
import io.cloudevents.v02.CloudEventImpl;
import io.cloudevents.cdi.EventTypeQualifier;
import javax.enterprise.event.Event;
@ -28,15 +28,16 @@ import java.util.UUID;
public class Router {
@Inject
private Event<CloudEvent<MyCustomEvent>> cloudEvent;
private Event<CloudEventImpl<MyCustomEvent>> cloudEvent;
public void routeMe() throws Exception {
final CloudEvent<MyCustomEvent> event = new CloudEventBuilder<MyCustomEvent>()
.type("Cloud.Storage.Item.Created")
.source(new URI("/trigger"))
.id(UUID.randomUUID().toString())
.data(new MyCustomEvent(...))
final CloudEventImpl<MyCustomEvent> event =
CloudEventBuilder.<MyCustomEvent>builder()
.withType("Cloud.Storage.Item.Created")
.withSource(new URI("/trigger"))
.withId(UUID.randomUUID().toString())
.withData(new MyCustomEvent(...))
.build();
cloudEvent.select(

View File

@ -20,7 +20,7 @@
<parent>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-parent</artifactId>
<version>0.2.2-SNAPSHOT</version>
<version>0.3.0-SNAPSHOT</version>
<relativePath>../</relativePath>
</parent>
@ -41,11 +41,11 @@
</dependencyManagement>
<dependencies>
<!-- Java EE 7 dependency -->
<!-- Java EE 8 dependency -->
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>7.0</version>
<version>8.0</version>
<scope>provided</scope>
</dependency>

Some files were not shown because too many files have changed in this diff Show More