commit
a00ddcad35
|
|
@ -3,7 +3,6 @@ language: java
|
|||
sudo: false
|
||||
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- openjdk8
|
||||
- openjdk9
|
||||
- openjdk10
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -19,13 +19,17 @@ 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.1</version>
|
||||
<version>0.2.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Application developers can now create strongly-typed CloudEvents, such as:
|
||||
|
||||
```java
|
||||
import io.cloudevents.v02.CloudEventBuilder;
|
||||
import io.cloudevents.v02.CloudEvent;
|
||||
import io.cloudevents.json.Json;
|
||||
|
||||
// given
|
||||
final String eventId = UUID.randomUUID().toString();
|
||||
final URI src = URI.create("/trigger");
|
||||
|
|
@ -34,11 +38,14 @@ final MyCustomEvent payload = ...
|
|||
|
||||
// passing in the given attributes
|
||||
final CloudEvent<MyCustomEvent> cloudEvent = new CloudEventBuilder<MyCustomEvent>()
|
||||
.type(eventType)
|
||||
.id(eventId)
|
||||
.source(src)
|
||||
.data(payload)
|
||||
.withType(eventType)
|
||||
.withId(eventId)
|
||||
.withSource(src)
|
||||
.withData(payload)
|
||||
.build();
|
||||
|
||||
// marshalling as json
|
||||
final String json = Json.encode(cloudEvent);
|
||||
```
|
||||
|
||||
## Possible Integrations
|
||||
|
|
|
|||
14
api/pom.xml
14
api/pom.xml
|
|
@ -42,6 +42,18 @@
|
|||
<artifactId>jackson-datatype-jdk8</artifactId>
|
||||
<version>${jackson.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
<artifactId>hibernate-validator</artifactId>
|
||||
<version>${hibernate-validator.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.el</artifactId>
|
||||
<version>${javax.el.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
|
|
@ -61,6 +73,8 @@
|
|||
|
||||
<properties>
|
||||
<jackson.version>2.9.6</jackson.version>
|
||||
<hibernate-validator.version>6.0.17.Final</hibernate-validator.version>
|
||||
<javax.el.version>3.0.1-b11</javax.el.version>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ public class DefaultCloudEventImpl<T> implements CloudEvent<T>, Serializable {
|
|||
this.schemaURL = schemaURL;
|
||||
}
|
||||
|
||||
@JsonAlias("contenttype")
|
||||
void setContentType(String contentType) {
|
||||
this.contentType = contentType;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,17 @@ public final class Json {
|
|||
throw new IllegalStateException("Failed to encode as JSON: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T> T fromInputStream(final InputStream inputStream,
|
||||
Class<T> clazz) {
|
||||
try {
|
||||
return MAPPER.readValue(inputStream, clazz);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to encode as JSON: "
|
||||
+ e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a given JSON string to a CloudEvent .
|
||||
*
|
||||
|
|
|
|||
|
|
@ -0,0 +1,112 @@
|
|||
package io.cloudevents.v02;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
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.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;
|
||||
|
||||
CloudEvent(String id, URI source, String specversion, String type,
|
||||
ZonedDateTime time, URI schemaurl, String contenttype,
|
||||
T data) {
|
||||
|
||||
this.id = id;
|
||||
this.source = source;
|
||||
this.specversion = specversion;
|
||||
this.type = type;
|
||||
|
||||
this.time = time;
|
||||
this.schemaurl = schemaurl;
|
||||
this.contenttype = contenttype;
|
||||
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,103 @@
|
|||
package io.cloudevents.v02;
|
||||
|
||||
import static java.lang.String.format;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
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;
|
||||
|
||||
/**
|
||||
* CloudEvent instances builder
|
||||
*
|
||||
* @author fabiojose
|
||||
*
|
||||
*/
|
||||
public class CloudEventBuilder<T> {
|
||||
|
||||
private static final String SPEC_VERSION = "0.2";
|
||||
private static final String MESSAGE_SEPARATOR = ", ";
|
||||
private static final String MESSAGE = "'%s' %s";
|
||||
private static final String ERR_MESSAGE = "invalid payload: %s";
|
||||
|
||||
private String type;
|
||||
private String id;
|
||||
private URI source;
|
||||
|
||||
private ZonedDateTime time;
|
||||
private URI schemaurl;
|
||||
private String contenttype;
|
||||
private T data;
|
||||
|
||||
private Validator getValidator() {
|
||||
return Validation.buildDefaultValidatorFactory().getValidator();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return An new {@link CloudEvent} 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);
|
||||
|
||||
Set<ConstraintViolation<CloudEvent<T>>> violations =
|
||||
getValidator().validate(event);
|
||||
|
||||
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 event;
|
||||
}
|
||||
|
||||
public CloudEventBuilder<T> withType(String type) {
|
||||
this.type = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
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> withTime(ZonedDateTime time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CloudEventBuilder<T> withSchemaurl(URI schemaurl) {
|
||||
this.schemaurl = schemaurl;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CloudEventBuilder<T> withContenttype(String contenttype) {
|
||||
this.contenttype = contenttype;
|
||||
return this;
|
||||
}
|
||||
|
||||
public CloudEventBuilder<T> withData(T data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,221 @@
|
|||
package io.cloudevents.v02;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
/**
|
||||
*
|
||||
* @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
|
||||
new CloudEventBuilder<Object>()
|
||||
.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
|
||||
new CloudEventBuilder<Object>()
|
||||
.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
|
||||
new CloudEventBuilder<Object>()
|
||||
.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
|
||||
new CloudEventBuilder<Object>()
|
||||
.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
|
||||
new CloudEventBuilder<Object>()
|
||||
.withId("id")
|
||||
.withType("type")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_id() {
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertEquals("id", ce.getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_source() {
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertEquals(URI.create("/source"), ce.getSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_type() {
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertEquals("type", ce.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_specversion() {
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertEquals("0.2", ce.getSpecversion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_time() {
|
||||
// setup
|
||||
ZonedDateTime expected = ZonedDateTime.now();
|
||||
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.withTime(expected)
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertTrue(ce.getTime().isPresent());
|
||||
assertEquals(expected, ce.getTime().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_schemaurl() {
|
||||
// setup
|
||||
URI expected = URI.create("/schema");
|
||||
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.withSchemaurl(expected)
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertTrue(ce.getSchemaurl().isPresent());
|
||||
assertEquals(expected, ce.getSchemaurl().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_contenttype() {
|
||||
// setup
|
||||
String expected = "application/json";
|
||||
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.withContenttype(expected)
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertTrue(ce.getContenttype().isPresent());
|
||||
assertEquals(expected, ce.getContenttype().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_data() {
|
||||
// setup
|
||||
String expected = "my data";
|
||||
|
||||
// act
|
||||
CloudEvent<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("id")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("type")
|
||||
.withData(expected)
|
||||
.build();
|
||||
|
||||
// assert
|
||||
assertTrue(ce.getData().isPresent());
|
||||
assertEquals(expected, ce.getData().get());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
package io.cloudevents.v02;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
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 io.cloudevents.json.Json;
|
||||
|
||||
/**
|
||||
*
|
||||
* @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<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.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.2"));
|
||||
|
||||
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<Object> ce =
|
||||
new CloudEventBuilder<>()
|
||||
.withId("x10")
|
||||
.withSource(URI.create("/source"))
|
||||
.withType("event-type")
|
||||
.withSchemaurl(URI.create("/schema"))
|
||||
.withContenttype("text/plain")
|
||||
.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"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_type() {
|
||||
// act
|
||||
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
|
||||
|
||||
// assert
|
||||
assertEquals("aws.s3.object.created", ce.getType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_id() {
|
||||
// act
|
||||
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
|
||||
|
||||
// assert
|
||||
assertEquals("C234-1234-1234", ce.getId());
|
||||
}
|
||||
|
||||
//should have time
|
||||
@Test
|
||||
public void should_have_time() {
|
||||
// act
|
||||
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
|
||||
|
||||
// assert
|
||||
assertTrue(ce.getTime().isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_source() {
|
||||
// act
|
||||
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
|
||||
|
||||
// assert
|
||||
assertEquals(URI.create("https://serverless.com"), ce.getSource());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_contenttype() {
|
||||
// act
|
||||
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
|
||||
|
||||
// assert
|
||||
assertTrue(ce.getContenttype().isPresent());
|
||||
assertEquals("application/json", ce.getContenttype().get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_have_specversion() {
|
||||
// act
|
||||
CloudEvent<?> ce = Json.fromInputStream(resourceOf("02_aws.json"), CloudEvent.class);
|
||||
|
||||
// assert
|
||||
assertEquals("0.2", ce.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("02_absent.json"), CloudEvent.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "aws.s3.object.created",
|
||||
"time": "2018-04-26T14:48:09.769Z",
|
||||
"source": "https://serverless.com",
|
||||
"contenttype": "application/json",
|
||||
"specversion": "0.2"
|
||||
}
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"id": "C234-1234-1234",
|
||||
"time": "2018-04-26T14:48:09.769Z",
|
||||
"source": "https://serverless.com",
|
||||
"contentType": "application/json",
|
||||
"contenttype": "application/json",
|
||||
"specversion": "0.2",
|
||||
"data":
|
||||
{ "s3SchemaVersion": "1.0",
|
||||
|
|
|
|||
Loading…
Reference in New Issue