Compare commits
160 Commits
Author | SHA1 | Date |
---|---|---|
|
0d96691f6d | |
|
c8a22b5175 | |
|
57f9867a5c | |
|
43f0d5b138 | |
|
efe7e01b75 | |
|
188c3c7085 | |
|
659bb8cc01 | |
|
9981635e7d | |
|
b54df5ca0c | |
|
a3decf768c | |
|
215e67f2c3 | |
|
514e00d75b | |
|
f7ac215bf1 | |
|
5f4abd144a | |
|
8d59d29bb1 | |
|
6ab42de007 | |
|
2e87988d0c | |
|
6d958b69d4 | |
|
1de03ad38a | |
|
4c5d0efb89 | |
|
01a9111d8b | |
|
e056d1b2d8 | |
|
efdf0ba866 | |
|
bd90d903ce | |
|
52a98d778c | |
|
010627e784 | |
|
a7904823c3 | |
|
e5a35ac472 | |
|
4d204cef89 | |
|
43afb6ceaa | |
|
9c6e7fd11e | |
|
98deac1599 | |
|
05baf9be8a | |
|
3add823e00 | |
|
5a03173dde | |
|
9ee16fb48c | |
|
0db4446163 | |
|
111fb55cfd | |
|
55fddb35fc | |
|
7b9d020acc | |
|
fb11b94f2b | |
|
eaef3becdd | |
|
b30324e916 | |
|
1f9fa13231 | |
|
a135755ec6 | |
|
677a2c2628 | |
|
826e099fc0 | |
|
76366338fc | |
|
4ef304115a | |
|
582feed520 | |
|
5ef1088a19 | |
|
698cdf7ad4 | |
|
4ebeab0e0f | |
|
4c81f3eacc | |
|
569e025cf0 | |
|
3614a4f5f4 | |
|
d64aff7327 | |
|
d1cff75230 | |
|
1591cb337a | |
|
f71303b7b7 | |
|
d59b33307a | |
|
e0d1961f35 | |
|
7c6b52ab30 | |
|
433ec5b274 | |
|
40fe91a5e0 | |
|
a43f90f4e2 | |
|
e488269510 | |
|
7d50c7fc7a | |
|
f1a86af656 | |
|
354f7a16ef | |
|
9132a13d81 | |
|
a491c85eb2 | |
|
6362bfbcd6 | |
|
f08a099ed9 | |
|
4139fb7e57 | |
|
adde53c817 | |
|
0dc10251ff | |
|
b9eaa2fcaa | |
|
f8d27b08bf | |
|
9125136530 | |
|
f35e6e610a | |
|
45ec85f8c1 | |
|
1d87fb7191 | |
|
8f9b741306 | |
|
2dd8ba95dd | |
|
1c29726e8c | |
|
6681205733 | |
|
a4bc7a8368 | |
|
4784f03e8c | |
|
ace6859ae0 | |
|
cc786251d5 | |
|
ceb06757a3 | |
|
8d91cdaee6 | |
|
d00ad967c0 | |
|
9231e6d230 | |
|
a94bc5c81c | |
|
32adfe9123 | |
|
0277ee4ae4 | |
|
202849307c | |
|
624ac693d8 | |
|
5a323942d3 | |
|
1587708805 | |
|
06c4ec5385 | |
|
73a3c370d5 | |
|
722f5205b3 | |
|
8ad857d8c7 | |
|
baa9b5927a | |
|
c41a2c3ba7 | |
|
3651cdae18 | |
|
ee4c85b1a1 | |
|
a0b0835180 | |
|
687e03bac5 | |
|
208b18c299 | |
|
9d45943844 | |
|
0a1a03db64 | |
|
7f355d10c1 | |
|
3234e30e55 | |
|
c8f10e9215 | |
|
ba9ccad5d2 | |
|
78355bb225 | |
|
2730ae4a13 | |
|
30fd6769eb | |
|
ff07dd8315 | |
|
928ebcfd6f | |
|
a4613c00d2 | |
|
69f0e20549 | |
|
e2b13109e4 | |
|
5e3bfc890f | |
|
13f8b56618 | |
|
a419d8bba3 | |
|
47bed5616d | |
|
baba37ccfd | |
|
32bcdcd3b9 | |
|
8b382734d9 | |
|
f05418cba9 | |
|
23cd08fcfd | |
|
70782da2c2 | |
|
48fc69e058 | |
|
e7e6e46bd5 | |
|
e523bfbfbf | |
|
12eee4da6e | |
|
d49ff9f69d | |
|
611f2292a7 | |
|
296230719b | |
|
d9592d5201 | |
|
00cdf9cb42 | |
|
bd11010138 | |
|
3a22557b83 | |
|
87c6915d9a | |
|
a7f87cf6cb | |
|
711277eacb | |
|
58570cf4d9 | |
|
24d108fe5d | |
|
f5d9b47c1c | |
|
59643c3368 | |
|
34483025df | |
|
e9d15daf28 | |
|
b89f45265b | |
|
a14f5eabec | |
|
5099b31f6c |
|
@ -0,0 +1,11 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -4,6 +4,8 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- '[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
@ -11,7 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 8, 11 ]
|
||||
java: [ 8, 11, 17 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
|
@ -19,8 +21,9 @@ jobs:
|
|||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- run: |
|
||||
mvn clean install -DskipTests -B
|
||||
mvn verify -B
|
||||
./mvnw clean install -DskipTests -B
|
||||
./mvnw verify -B
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
|
@ -31,13 +34,13 @@ jobs:
|
|||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-id: central # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-username: MAVEN_USERNAME # env variable for username in deploy
|
||||
server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
|
||||
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
|
||||
- name: Publish to Apache Maven Central
|
||||
run: mvn clean deploy -Drelease -DskipTests -B
|
||||
run: ./mvnw clean deploy -Drelease -DskipTests -B
|
||||
env:
|
||||
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
|
|
@ -5,6 +5,14 @@ on:
|
|||
version:
|
||||
description: 'Version to bump (without prepending "v")'
|
||||
required: true
|
||||
maven-modules:
|
||||
description: "Whether to bump versions in pom.xml files"
|
||||
type: choice
|
||||
required: true
|
||||
default: 'true'
|
||||
options:
|
||||
- 'true'
|
||||
- 'false'
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
|
@ -19,7 +27,8 @@ jobs:
|
|||
with:
|
||||
java-version: 8
|
||||
- name: Bump version using Maven
|
||||
run: 'mvn versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -B'
|
||||
if: ${{ inputs.maven-modules == 'true' }}
|
||||
run: './mvnw versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -B'
|
||||
- name: Bump version in docs
|
||||
if: ${{ !endsWith(github.event.inputs.version, 'SNAPSHOT') }}
|
||||
run: 'find . -type f -name "*.md" -exec sed -i -e "s+<version>[a-zA-Z0-9.-]*<\/version>+<version>$NEW_VERSION</version>+g" {} +'
|
||||
|
|
|
@ -4,20 +4,24 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- '[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 8, 11 ]
|
||||
java: [ 8, 11, 17 ]
|
||||
name: Java ${{ matrix.java }} Test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
- run: |
|
||||
mvn clean install -DskipTests -B
|
||||
mvn verify -B
|
||||
./mvnw clean install -DskipTests -B
|
||||
./mvnw verify -B
|
||||
./mvnw javadoc:javadoc
|
||||
|
|
|
@ -42,3 +42,8 @@ _site/
|
|||
.sass-cache/
|
||||
.jekyll-cache/
|
||||
.jekyll-metadata
|
||||
|
||||
# MacOS
|
||||
*.DS_Store
|
||||
/http/restful-ws-jakarta/src/main/*
|
||||
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
# Pull Request Guidelines
|
||||
# Contributing to CloudEvents' Java SDK
|
||||
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
We welcome contributions from the community! Please take some time to become
|
||||
acquainted with the process before submitting a pull request. There are just
|
||||
a few things to keep in mind.
|
||||
|
||||
# Pull Requests
|
||||
|
||||
Typically, a pull request should relate to an existing issue. If you have
|
||||
found a bug, want to add an improvement, or suggest an API change, please
|
||||
create an issue before proceeding with a pull request. For very minor changes
|
||||
such as typos in the documentation this isn't really necessary.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
Here you will find step by step guidance for creating, submitting and updating
|
||||
a pull request in this repository. We hope it will help you have an easy time
|
||||
|
@ -8,7 +23,7 @@ your code. Thanks for getting involved! :rocket:
|
|||
* [Getting Started](#getting-started)
|
||||
* [Branches](#branches)
|
||||
* [Commit Messages](#commit-messages)
|
||||
* [Staying current with master](#staying-current-with-master)
|
||||
* [Staying current with main](#staying-current-with-main)
|
||||
* [Submitting and Updating a Pull Request](#submitting-and-updating-a-pull-request)
|
||||
* [Congratulations!](#congratulations)
|
||||
|
||||
|
@ -32,7 +47,7 @@ organized.
|
|||
|
||||
```console
|
||||
git fetch upstream
|
||||
git reset --hard upstream/master
|
||||
git reset --hard upstream/main
|
||||
git checkout FETCH_HEAD
|
||||
git checkout -b 48-fix-http-agent-error
|
||||
```
|
||||
|
@ -87,19 +102,19 @@ Date: Thu Feb 2 11:41:15 2018 -0800
|
|||
Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will
|
||||
be rejected by the automated DCO check.
|
||||
|
||||
## Staying Current with `master`
|
||||
## Staying Current with `main`
|
||||
|
||||
As you are working on your branch, changes may happen on `master`. Before
|
||||
As you are working on your branch, changes may happen on `main`. Before
|
||||
submitting your pull request, be sure that your branch has been updated
|
||||
with the latest commits.
|
||||
|
||||
```console
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
git rebase upstream/main
|
||||
```
|
||||
|
||||
This may cause conflicts if the files you are changing on your branch are
|
||||
also changed on master. Error messages from `git` will indicate if conflicts
|
||||
also changed on main. Error messages from `git` will indicate if conflicts
|
||||
exist and what files need attention. Resolve the conflicts in each file, then
|
||||
continue with the rebase with `git rebase --continue`.
|
||||
|
||||
|
@ -116,15 +131,15 @@ git push -f origin 48-fix-http-agent-error
|
|||
Before submitting a pull request, you should make sure that all of the tests
|
||||
successfully pass.
|
||||
|
||||
Once you have sent your pull request, `master` may continue to evolve
|
||||
before your pull request has landed. If there are any commits on `master`
|
||||
Once you have sent your pull request, `main` may continue to evolve
|
||||
before your pull request has landed. If there are any commits on `main`
|
||||
that conflict with your changes, you may need to update your branch with
|
||||
these changes before the pull request can land. Resolve conflicts the same
|
||||
way as before.
|
||||
|
||||
```console
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
git rebase upstream/main
|
||||
# fix any potential conflicts
|
||||
git push -f origin 48-fix-http-agent-error
|
||||
```
|
||||
|
@ -141,7 +156,7 @@ for details.
|
|||
|
||||
```console
|
||||
git commit -m "fixup: fix typo"
|
||||
git rebase -i upstream/master # follow git instructions
|
||||
git rebase -i upstream/main # follow git instructions
|
||||
```
|
||||
|
||||
Once you have rebased your commits, you can force push to your fork as before.
|
|
@ -0,0 +1,5 @@
|
|||
# Maintainers
|
||||
|
||||
Current active maintainers of this SDK:
|
||||
|
||||
- [Pierangelo Di Pilato](https://github.com/pierDipi)
|
|
@ -1,17 +1,5 @@
|
|||
# Maintainer's Guide
|
||||
|
||||
## Release Process
|
||||
|
||||
The release process is automated with Github actions. In order to perform a release:
|
||||
|
||||
1. Check if master CI pass.
|
||||
1. Open the Github repository main page and go in the tab "Actions". Trigger the workflow "Bump version" and insert the new version to release. This will create a new release PR.
|
||||
1. Check the release PR, merge it and cleanup the created branch.
|
||||
1. Wait for the CI to complete the deploy of the modules to OSSRH.
|
||||
1. Using the Github UI, create a new release, specifying the release notes and the tag to use.
|
||||
1. Trigger again the workflow "Bump version" to bump versions back to a snapshot version.
|
||||
1. Check the snapshot release PR, merge it and cleanup the created branch.
|
||||
|
||||
## Tips
|
||||
|
||||
Here are a few tips for repository maintainers.
|
||||
|
@ -38,14 +26,14 @@ When landing pull requests, be sure to check the first line uses an appropriate
|
|||
|
||||
## Branch Management
|
||||
|
||||
The `master` branch is the bleeding edge. New major versions of the module
|
||||
The `main` branch is the bleeding edge. New major versions of the module
|
||||
are cut from this branch and tagged. If you intend to submit a pull request
|
||||
you should use `master HEAD` as your starting point.
|
||||
you should use `main HEAD` as your starting point.
|
||||
|
||||
Each major release will result in a new branch and tag. For example, the
|
||||
release of version 1.0.0 of the module will result in a `v1.0.0` tag on the
|
||||
release commit, and a new branch `v1.x.y` for subsequent minor and patch
|
||||
level releases of that major version. However, development will continue
|
||||
apace on `master` for the next major version - e.g. 2.0.0. Version branches
|
||||
apace on `main` for the next major version - e.g. 2.0.0. Version branches
|
||||
are only created for each major version. Minor and patch level releases
|
||||
are simply tagged.
|
67
README.md
67
README.md
|
@ -4,10 +4,20 @@
|
|||
[](https://maven-badges.herokuapp.com/maven-central/io.cloudevents/cloudevents-parent)
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
|
||||
A Java API for the
|
||||
[CloudEvents specification](https://github.com/cloudevents/spec)
|
||||
The Java SDK for CloudEvents is a collection of Java packages to adopt
|
||||
[CloudEvents](https://github.com/cloudevents/spec) in your Java application.
|
||||
|
||||
Look at https://cloudevents.github.io/sdk-java/ for more documentation.
|
||||
Using the Java SDK you can:
|
||||
|
||||
- Access, create and manipulate `CloudEvent` inside your application.
|
||||
- Serialize and deserialize `CloudEvent` back and forth using the _CloudEvents
|
||||
Event Format_, like Json.
|
||||
- Read and write `CloudEvent` back and forth to HTTP, Kafka, AMQP using the
|
||||
_CloudEvents Protocol Binding_ implementations we provide for a wide range
|
||||
of well known Java frameworks/libraries.
|
||||
|
||||
To check out the complete documentation and how to get started, look at the dedicated website
|
||||
https://cloudevents.github.io/sdk-java/.
|
||||
|
||||
## Status
|
||||
|
||||
|
@ -32,46 +42,45 @@ Supported features of the specification:
|
|||
| - [Vert.x](http/vertx) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jakarta Restful WS](http/restful-ws) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Basic](http/basic) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Spring](spring) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [http4k][http4k]<sup>†</sup>| :heavy_check_mark: | :heavy_check_mark: |
|
||||
| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jackson](formats/json-jackson) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Proto](formats/protobuf) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| [Kafka Protocol Binding](kafka) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
|
||||
## 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 `1.0` 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).
|
||||
<sub>† Source/artifacts hosted externally</sub>
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available at https://cloudevents.github.io/sdk-java/
|
||||
Documentation is available at https://cloudevents.github.io/sdk-java/.
|
||||
|
||||
Javadocs are available on [javadoc.io](https://www.javadoc.io):
|
||||
|
||||
- [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
- [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
- [cloudevents-avro-compact](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact)
|
||||
- [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
|
||||
- [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
|
||||
- [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml)
|
||||
- [cloudevents-http-basic](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-basic)
|
||||
- [cloudevents-http-restful-ws](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||
- [cloudevents-http-vertx](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-vertx)
|
||||
- [cloudevents-kafka](https://www.javadoc.io/doc/io.cloudevents/cloudevents-kafka)
|
||||
- [cloudevents-amqp](https://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp)
|
||||
- [cloudevents-spring](https://www.javadoc.io/doc/io.cloudevents/cloudevents-spring)
|
||||
|
||||
You can check out the examples in the [examples](examples) directory.
|
||||
|
||||
## Used By
|
||||
|
||||
| [Occurrent](https://occurrent.org) | [Knative Eventing](https://github.com/knative-sandbox/eventing-kafka-broker) |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <a href="https://occurrent.org"><img src="https://raw.githubusercontent.com/johanhaleby/occurrent/master/occurrent-logo-196x196.png" width="98" height="98" alt="Occurrent" title="Occurrent - Event Sourcing Utilities for the JVM"></img></a> | <a href="https://github.com/knative-sandbox/eventing-kafka-broker"><img src="https://cloudevents.io/img/logos/integrations/knative.png" height="98"></img></a> |
|
||||
| [Occurrent](https://occurrent.org) | [Knative Eventing](https://github.com/knative-sandbox/eventing-kafka-broker )| [http4k][http4k] |
|
||||
| ---------------------------------- | ---------------------------------------------------------------------------- | ---------------|
|
||||
| <a href="https://occurrent.org"><img src="https://raw.githubusercontent.com/johanhaleby/occurrent/master/occurrent-logo-196x196.png" width="98" height="98" alt="Occurrent" title="Occurrent - Event Sourcing Utilities for the JVM"></img></a> | <a href="https://github.com/knative-sandbox/eventing-kafka-broker"><img src="https://cloudevents.io/img/logos/integrations/knative.png" height="98"></img></a> | <a href="https://www.http4k.org/guide/modules/cloud_events/"><img src="https://http4k.org/img/favicon-310.png" height="98" alt="http4k" title="http4k"></img></a> | |
|
||||
|
||||
## Community
|
||||
|
||||
|
@ -90,11 +99,25 @@ You can check out the examples in the [examples](examples) directory.
|
|||
|
||||
Each SDK may have its own unique processes, tooling and guidelines, common
|
||||
governance related material can be found in the
|
||||
[CloudEvents `community`](https://github.com/cloudevents/spec/tree/master/community)
|
||||
[CloudEvents `community`](https://github.com/cloudevents/spec/tree/main/docs)
|
||||
directory. In particular, in there you will find information concerning how SDK
|
||||
projects are
|
||||
[managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md),
|
||||
[guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md)
|
||||
[managed](https://github.com/cloudevents/spec/blob/main/docs/SDK-GOVERNANCE.md),
|
||||
[guidelines](https://github.com/cloudevents/spec/blob/main/docs/SDK-maintainer-guidelines.md)
|
||||
for how PR reviews and approval, and our
|
||||
[Code of Conduct](https://github.com/cloudevents/spec/blob/master/community/GOVERNANCE.md#additional-information)
|
||||
[Code of Conduct](https://github.com/cloudevents/spec/blob/main/docs/GOVERNANCE.md#additional-information)
|
||||
information.
|
||||
|
||||
If there is a security concern with one of the CloudEvents specifications, or
|
||||
with one of the project's SDKs, please send an email to
|
||||
[cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io).
|
||||
|
||||
## Additional SDK Resources
|
||||
|
||||
- [List of current active maintainers](MAINTAINERS.md)
|
||||
- [How to contribute to the project](CONTRIBUTING.md)
|
||||
- [SDK's License](LICENSE)
|
||||
- [SDK's Release process](RELEASING.md)
|
||||
- [SDK Maintainer's guide](MAINTAINERS_GUIDE.md)
|
||||
|
||||
[http4k]: https://www.http4k.org/guide/reference/cloud_events/
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Release Process
|
||||
|
||||
The release process is automated with Github actions. In order to perform a release:
|
||||
|
||||
1. Check if main CI pass.
|
||||
1. Open the Github repository main page and go in the tab "Actions". Trigger the workflow "Bump version" and insert the new version to release. This will create a new release PR.
|
||||
1. Check the release PR, merge it and cleanup the created branch.
|
||||
1. Wait for the CI to complete the deploy of the modules to OSSRH.
|
||||
1. Using the Github UI, create a new release, specifying the release notes and the tag to use.
|
||||
1. Trigger again the workflow "Bump version" to bump versions back to a snapshot version.
|
||||
1. Check the snapshot release PR, merge it and cleanup the created branch.
|
||||
|
|
@ -1,35 +1,5 @@
|
|||
# AMQP Protocol Binding
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp-proton)
|
||||
Javadoc: [](http://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp-proton)
|
||||
|
||||
This module implements `MessageReader` and `MessageWriter` using the Qpid Proton library. It can be used with Qpid Proton or any integrations based on Qpid Proton (e.g vertx-proton).
|
||||
|
||||
For Maven based projects, use the following to configure the `proton` AMQP binding for CloudEvents:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<version>2.0.0.RC1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Sending and Receiving CloudEvents
|
||||
|
||||
To send and receive CloudEvents we use `MessageWriter` and `MessageReader`, respectively.
|
||||
This module offers factory methods for creation of those in `ProtonAmqpMessageFactory`.
|
||||
|
||||
```java
|
||||
public class ProtonAmqpMessageFactory {
|
||||
public static MessageReader createReader(final Message message);
|
||||
public static MessageReader createReader(final String contentType, final byte[] payload);
|
||||
public static MessageReader createReader(final String contentType, final ApplicationProperties props, final byte[] payload);
|
||||
public static MessageWriter createWriter();
|
||||
}
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
The example uses the vertx-proton integration to send/receive CloudEvent messages over AMQP.
|
||||
* [Vertx AmqpServer](../../examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpServer.java)
|
||||
* [Vertx AmqpClient](../../examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpClient.java)
|
||||
Documentation: https://cloudevents.github.io/sdk-java/amqp-proton
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
|
@ -14,10 +14,12 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<protonj.version>0.33.7</protonj.version>
|
||||
<protonj.version>0.34.1</protonj.version>
|
||||
<jsr305.version>3.0.2</jsr305.version>
|
||||
<module-name>io.cloudevents.amqp.proton</module-name>
|
||||
</properties>
|
||||
<dependencies>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.qpid</groupId>
|
||||
<artifactId>proton-j</artifactId>
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package io.cloudevents.amqp;
|
||||
|
||||
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.amqp.impl.AmqpConstants;
|
||||
import io.cloudevents.amqp.impl.ProtonAmqpBinaryMessageReader;
|
||||
import io.cloudevents.amqp.impl.ProtonAmqpMessageWriter;
|
||||
|
@ -25,13 +25,17 @@ import io.cloudevents.core.message.MessageReader;
|
|||
import io.cloudevents.core.message.MessageWriter;
|
||||
import io.cloudevents.core.message.impl.GenericStructuredMessageReader;
|
||||
import io.cloudevents.core.message.impl.MessageUtils;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Section;
|
||||
import org.apache.qpid.proton.message.Message;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* A factory class providing convenience methods for creating MessageReader and MessageWriter instances based on Qpid Proton.
|
||||
* A factory class providing convenience methods for creating {@link MessageReader} and {@link MessageWriter} instances based on Qpid Proton {@link Message}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class ProtonAmqpMessageFactory {
|
||||
|
@ -41,62 +45,47 @@ public final class ProtonAmqpMessageFactory {
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates a MessageReader to read a proton-based {@link Message}.
|
||||
* <p>
|
||||
* This implementation simply calls {@link #createReader(String, ApplicationProperties, byte[])}.
|
||||
* Creates a {@link MessageReader} to read a proton-based {@link Message}.
|
||||
* This reader is able to read both binary and structured encoded {@link io.cloudevents.CloudEvent}.
|
||||
*
|
||||
* @param message The proton message to read from.
|
||||
*
|
||||
* @return A message reader that can read the given proton message to a cloud event representation.
|
||||
* @param message The proton {@link Message} to read from.
|
||||
* @return A {@link MessageReader} that can read the given proton {@link Message} to a {@link io.cloudevents.CloudEvent} representation.
|
||||
* @throws CloudEventRWException if something goes wrong while resolving the {@link SpecVersion} or if the message has unknown encoding
|
||||
* @see #createReader(String, ApplicationProperties, Section)
|
||||
*/
|
||||
public static MessageReader createReader(final Message message) {
|
||||
|
||||
final byte[] payload = AmqpConstants.getPayloadAsByteArray(message.getBody());
|
||||
return createReader(message.getContentType(), message.getApplicationProperties(), payload);
|
||||
public static MessageReader createReader(final Message message) throws CloudEventRWException {
|
||||
return createReader(message.getContentType(), message.getApplicationProperties(), message.getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MessageReader using the content-type property and payload of a proton-based message.
|
||||
* <p>
|
||||
* This method simply calls {@link #createReader(String, ApplicationProperties, byte[])}.
|
||||
* Creates a MessageReader to read using the {@code content-type} property, {@code application-properties} and data payload
|
||||
* of a proton-based {@link Message}. This reader is able to read both binary and structured encoded {@link io.cloudevents.CloudEvent}.
|
||||
*
|
||||
* @param contentType The content-type of the message payload.
|
||||
* @param payload The message payload in bytes.
|
||||
* @return A message reader capable of representing a CloudEvent from
|
||||
* a message <em>content-type</em> property and <em>application-data</em>.
|
||||
* @param contentType The {@code content-type} of the message payload.
|
||||
* @param props The {@code application-properties} section of the proton-message containing cloud event metadata (attributes and/or extensions).
|
||||
* @param body The message body or {@code null} if the message does not contain any body.
|
||||
* @return A {@link MessageReader} capable of representing a {@link io.cloudevents.CloudEvent} from the {@code application-properties},
|
||||
* {@code content-type} and payload of a proton message.
|
||||
* @throws CloudEventRWException if something goes wrong while resolving the {@link SpecVersion} or if the message has unknown encoding
|
||||
*/
|
||||
public static MessageReader createReader(final String contentType, final byte[] payload) {
|
||||
return createReader(contentType, null, payload);
|
||||
public static MessageReader createReader(final String contentType, final ApplicationProperties props, @Nullable final Section body) throws CloudEventRWException {
|
||||
final byte[] payload = AmqpConstants.getPayloadAsByteArray(body);
|
||||
return MessageUtils.parseStructuredOrBinaryMessage(
|
||||
() -> contentType,
|
||||
format -> new GenericStructuredMessageReader(format, payload),
|
||||
() -> AmqpConstants.getApplicationProperty(props, AmqpConstants.APP_PROPERTY_SPEC_VERSION, String.class),
|
||||
sv -> new ProtonAmqpBinaryMessageReader(sv, props, contentType, payload)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MessageWriter capable of translating both a structured and binary CloudEvent
|
||||
* to a proton-based AMQP 1.0 representation.
|
||||
* Creates a {@link MessageWriter} capable of translating both a structured and binary CloudEvent
|
||||
* to a proton-based AMQP 1.0 {@link Message}.
|
||||
*
|
||||
* @return A message writer to read structured and binary cloud event from a proton-based message.
|
||||
* @return A {@link MessageWriter} to write a {@link io.cloudevents.CloudEvent} to Proton {@link Message} using structured or binary encoding.
|
||||
*/
|
||||
public static MessageWriter<CloudEventWriter<Message>, Message> createWriter() {
|
||||
return new ProtonAmqpMessageWriter<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MessageReader to read using the content-type property, application-propeties and data payload
|
||||
* of a proton-based message.
|
||||
*
|
||||
* @param contentType The content-type of the message payload.
|
||||
* @param props The application-properties section of the proton-message containing cloud event metadata (attributes and/or extensions).
|
||||
* @param payload The message payload in bytes or {@code null} if the message does not contain any payload.
|
||||
* @return A message reader capable of representing a CloudEvent from the application-properties,
|
||||
* content-type and payload of a proton message.
|
||||
*/
|
||||
public static MessageReader createReader(final String contentType, final ApplicationProperties props, final byte[] payload) {
|
||||
|
||||
return MessageUtils.parseStructuredOrBinaryMessage(
|
||||
() -> contentType,
|
||||
format -> new GenericStructuredMessageReader(format, payload),
|
||||
() -> AmqpConstants.getApplicationProperty(props, AmqpConstants.APP_PROPERTY_SPEC_VERSION, String.class),
|
||||
sv -> new ProtonAmqpBinaryMessageReader(sv, props, contentType, payload));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,22 +17,20 @@
|
|||
|
||||
package io.cloudevents.amqp.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import org.apache.qpid.proton.amqp.Binary;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Data;
|
||||
import org.apache.qpid.proton.message.Message;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.core.message.MessageWriter;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventExtensionsWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import org.apache.qpid.proton.amqp.Binary;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Data;
|
||||
import org.apache.qpid.proton.message.Message;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A proton-based MessageWriter capable of writing both structured and binary CloudEvent messages to an AMQP 1.0 representation as
|
||||
|
@ -53,33 +51,28 @@ public final class ProtonAmqpMessageWriter<R> implements MessageWriter<CloudEven
|
|||
}
|
||||
|
||||
@Override
|
||||
public CloudEventAttributesWriter withAttribute(final String name, final String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
if (name.equals(CloudEventV1.DATACONTENTTYPE)) {
|
||||
message.setContentType(value);
|
||||
} else {
|
||||
// for now, extensions are mapped to application-properties
|
||||
// see https://github.com/cloudevents/sdk-java/issues/30#issuecomment-723982190
|
||||
if (applicationProperties == null) {
|
||||
throw new IllegalStateException("This Writer is not initialized");
|
||||
}
|
||||
applicationProperties.getValue().put(AmqpConstants.ATTRIBUTES_TO_PROPERTYNAMES.get(name), value);
|
||||
String propName = AmqpConstants.ATTRIBUTES_TO_PROPERTYNAMES.get(name);
|
||||
if (propName == null) {
|
||||
propName = name;
|
||||
}
|
||||
applicationProperties.getValue().put(propName, value);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventExtensionsWriter withExtension(final String name, final String value) throws CloudEventRWException {
|
||||
// for now, extensions are mapped to application-properties
|
||||
// see https://github.com/cloudevents/sdk-java/issues/30#issuecomment-723982190
|
||||
if (applicationProperties == null) {
|
||||
throw new IllegalStateException("This Writer is not initialized");
|
||||
}
|
||||
applicationProperties.getValue().put(name, value);
|
||||
return null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtonAmqpMessageWriter<R> create(final SpecVersion version) {
|
||||
if (applicationProperties == null) {
|
||||
applicationProperties = new ApplicationProperties(new HashMap<String, Object>());
|
||||
applicationProperties = new ApplicationProperties(new HashMap<>());
|
||||
}
|
||||
applicationProperties.getValue().put(AmqpConstants.APP_PROPERTY_SPEC_VERSION, version.toString());
|
||||
return this;
|
||||
|
@ -105,5 +98,4 @@ public final class ProtonAmqpMessageWriter<R> implements MessageWriter<CloudEven
|
|||
message.setApplicationProperties(applicationProperties);
|
||||
return message;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -17,18 +17,6 @@
|
|||
|
||||
package io.cloudevents.amqp;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.amqp.impl.AmqpConstants;
|
||||
|
@ -39,6 +27,19 @@ import io.cloudevents.core.test.Data;
|
|||
import io.cloudevents.core.v03.CloudEventV03;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.types.Time;
|
||||
import org.apache.qpid.proton.amqp.Binary;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Section;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests verifying the behavior of the {@code ProtonAmqpMessageFactory}.
|
||||
|
@ -53,7 +54,8 @@ public class ProtonAmqpMessageFactoryTest {
|
|||
@MethodSource("binaryTestArguments")
|
||||
public void readBinary(final Map<String, Object> props, final String contentType, final byte[] body,
|
||||
final CloudEvent event) {
|
||||
final MessageReader amqpReader = ProtonAmqpMessageFactory.createReader(contentType, new ApplicationProperties(props), body);
|
||||
final Section bodySection = body != null ? new org.apache.qpid.proton.amqp.messaging.Data(new Binary(body)) : null;
|
||||
final MessageReader amqpReader = ProtonAmqpMessageFactory.createReader(contentType, new ApplicationProperties(props), bodySection);
|
||||
assertThat(amqpReader.getEncoding()).isEqualTo(Encoding.BINARY);
|
||||
assertThat(amqpReader.toEvent()).isEqualTo(event);
|
||||
}
|
||||
|
@ -64,7 +66,7 @@ public class ProtonAmqpMessageFactoryTest {
|
|||
final String contentType = CSVFormat.INSTANCE.serializedContentType() + "; charset=utf8";
|
||||
final byte[] contentPayload = CSVFormat.INSTANCE.serialize(event);
|
||||
|
||||
final MessageReader amqpReader = ProtonAmqpMessageFactory.createReader(contentType, null, contentPayload);
|
||||
final MessageReader amqpReader = ProtonAmqpMessageFactory.createReader(contentType, null, new org.apache.qpid.proton.amqp.messaging.Data(new Binary(contentPayload)));
|
||||
assertThat(amqpReader.getEncoding()).isEqualTo(Encoding.STRUCTURED);
|
||||
assertThat(amqpReader.toEvent()).isEqualTo(event);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,5 @@
|
|||
# CloudEvents API
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
Javadoc: [](http://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>2.0.0.RC1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
This package provides the base interfaces used by the SDK. In particular:
|
||||
|
||||
- `CloudEvent` is the main interface representing a read only CloudEvent in-memory representation
|
||||
- `Extension` represents a _materialized_ in-memory representation of a CloudEvent extension
|
||||
- `SpecVersion` is an enum of CloudEvents' specification versions supported by this SDK version.
|
||||
- `CloudEventVisitor`/`CloudEventVisitable` are the interfaces used by the SDK to implement protocol bindings/event formats.
|
||||
A 3rd party implementer can implement these interfaces directly in its `CloudEvent` in order
|
||||
to customize/implement efficiently the marshalling/unmarshalling process.
|
||||
These interfaces are optional and, if your `CloudEvent` doesn't implement it, a default implementation is provided by the SDK.
|
||||
|
||||
`cloudevents-core` provides the implementation of these interfaces, and a 3rd party implementer can grab this package
|
||||
to implement specialized CloudEvent in-memory representations.
|
||||
Documentation: https://cloudevents.github.io/sdk-java/api
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
|
|
|
@ -20,12 +20,12 @@ import io.cloudevents.lang.Nullable;
|
|||
|
||||
/**
|
||||
* Interface representing an in memory read only representation of a CloudEvent,
|
||||
* as specified by the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md">CloudEvents specification</a>
|
||||
* as specified by the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md">CloudEvents specification</a>.
|
||||
*/
|
||||
public interface CloudEvent extends CloudEventContext {
|
||||
|
||||
/**
|
||||
* The event data
|
||||
* @return The event data, if any, otherwise {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
CloudEventData getData();
|
||||
|
|
|
@ -23,10 +23,10 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Materialized CloudEvent Extension interface to read/write the extension attributes key/values.
|
||||
* Materialized CloudEvent extension interface to read/write the extension attributes key/values.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface Extension {
|
||||
public interface CloudEventExtension {
|
||||
|
||||
/**
|
||||
* Fill this materialized extension with values from a {@link CloudEventExtensions} implementation.
|
||||
|
@ -39,7 +39,7 @@ public interface Extension {
|
|||
* Get the attribute of extension named {@code key}.
|
||||
*
|
||||
* @param key the name of the extension attribute
|
||||
* @return the extension value in one of the valid types String/Number/Boolean
|
||||
* @return the extension value in one of the valid types String/Number/Boolean/URI/OffsetDateTime/byte[]
|
||||
* @throws IllegalArgumentException if the key is unknown to this extension
|
||||
*/
|
||||
@Nullable
|
|
@ -29,11 +29,17 @@ import java.util.stream.Stream;
|
|||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public enum SpecVersion {
|
||||
/**
|
||||
* @see <a href="https://github.com/cloudevents/spec/releases/tag/v0.3">CloudEvents release v0.3</a>
|
||||
*/
|
||||
V03(
|
||||
"0.3",
|
||||
Arrays.asList("specversion", "id", "type", "source"),
|
||||
Arrays.asList("datacontenttype", "datacontentencoding", "schemaurl", "subject", "time")
|
||||
),
|
||||
/**
|
||||
* @see <a href="https://github.com/cloudevents/spec/releases/tag/v1.0">CloudEvents release v1.0</a>
|
||||
*/
|
||||
V1(
|
||||
"1.0",
|
||||
Arrays.asList("specversion", "id", "type", "source"),
|
||||
|
|
|
@ -27,6 +27,9 @@ import static java.lang.annotation.ElementType.*;
|
|||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static javax.annotation.meta.When.MAYBE;
|
||||
|
||||
/**
|
||||
* This annotation is used to define a method parameter or return type as nullable.
|
||||
*/
|
||||
@Target(value = {METHOD, PARAMETER, FIELD})
|
||||
@Retention(value = RUNTIME)
|
||||
@Documented
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.rw;
|
||||
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* Interface to write the attributes from a {@link io.cloudevents.rw.CloudEventReader} to a new representation.
|
||||
*/
|
||||
public interface CloudEventAttributesWriter {
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link String}. This setter should not be invoked for specversion, because the built Visitor already
|
||||
* has the information through the {@link CloudEventWriterFactory}.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
*/
|
||||
CloudEventAttributesWriter withAttribute(String name, String value) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
*/
|
||||
default CloudEventAttributesWriter withAttribute(String name, URI value) throws CloudEventRWException {
|
||||
return withAttribute(name, value == null ? null : value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link OffsetDateTime} attribute.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
*/
|
||||
default CloudEventAttributesWriter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
return withAttribute(name, value == null ? null : Time.writeTime(name, value));
|
||||
}
|
||||
|
||||
}
|
|
@ -28,19 +28,11 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
public interface CloudEventContextReader {
|
||||
|
||||
/**
|
||||
* Visit self attributes using the provided writer
|
||||
* Read the context attributes and extensions using the provided writer
|
||||
*
|
||||
* @param writer Attributes writer
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @param writer context writer
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
*/
|
||||
void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Visit self extensions using the provided writer
|
||||
*
|
||||
* @param visitor Extensions writer
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
*/
|
||||
void readExtensions(CloudEventExtensionsWriter visitor) throws CloudEventRWException;
|
||||
void readContext(CloudEventContextWriter writer) throws CloudEventRWException;
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.rw;
|
||||
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Interface to write the context attributes/extensions from a {@link io.cloudevents.rw.CloudEventContextReader} to a new representation.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface CloudEventContextWriter {
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link String}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link OffsetDateTime} attribute.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, Time.writeTime(name, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Number}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*
|
||||
* @deprecated CloudEvent specification only permits {@link Integer} type as a
|
||||
* numeric value.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Integer}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Boolean} attribute.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with a binary type.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, byte[] value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, Base64.getEncoder().encodeToString(value));
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
|
||||
/**
|
||||
* Interface to convert a {@link CloudEventData} instance to another one.
|
||||
*
|
||||
* @param <R> the returned {@link CloudEventData} from this mapper.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@ParametersAreNonnullByDefault
|
||||
|
@ -38,7 +40,7 @@ public interface CloudEventDataMapper<R extends CloudEventData> {
|
|||
R map(CloudEventData data) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* No-op identity mapper which can be used as default when no mapper is provided.
|
||||
* @return No-op identity mapper which can be used as default when no mapper is provided.
|
||||
*/
|
||||
static CloudEventDataMapper<CloudEventData> identity() {
|
||||
return d -> d;
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* 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.rw;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Interface to write the extensions from a {@link io.cloudevents.rw.CloudEventReader} to a new representation.
|
||||
*/
|
||||
public interface CloudEventExtensionsWriter {
|
||||
|
||||
/**
|
||||
* Set an extension with type {@link String}.
|
||||
*
|
||||
* @param name name of the extension
|
||||
* @param value value of the extension
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
*/
|
||||
CloudEventExtensionsWriter withExtension(String name, String value) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
*
|
||||
* @param name name of the extension
|
||||
* @param value value of the extension
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
*/
|
||||
default CloudEventExtensionsWriter withExtension(String name, Number value) throws CloudEventRWException {
|
||||
return withExtension(name, value == null ? null : value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Boolean} attribute.
|
||||
*
|
||||
* @param name name of the extension
|
||||
* @param value value of the extension
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
*/
|
||||
default CloudEventExtensionsWriter withExtension(String name, Boolean value) throws CloudEventRWException {
|
||||
return withExtension(name, value == null ? null : value.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This class is the exception Protocol Binding and Event Format implementers can use to signal errors while serializing/deserializing CloudEvent.
|
||||
*/
|
||||
|
@ -35,9 +37,9 @@ public class CloudEventRWException extends RuntimeException {
|
|||
*/
|
||||
INVALID_ATTRIBUTE_NAME,
|
||||
/**
|
||||
* The extension name is not valid,
|
||||
* because it doesn't follow the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#attribute-naming-convention">naming convention</a>
|
||||
* enforced by the CloudEvents spec.
|
||||
* The extension name is not valid because it doesn't follow the naming convention enforced by the CloudEvents spec.
|
||||
*
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#attribute-naming-convention">naming convention</a>
|
||||
*/
|
||||
INVALID_EXTENSION_NAME,
|
||||
/**
|
||||
|
@ -83,10 +85,17 @@ public class CloudEventRWException extends RuntimeException {
|
|||
this.kind = kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link CloudEventRWExceptionKind} associated to this exception instance.
|
||||
*/
|
||||
public CloudEventRWExceptionKind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param specVersion the invalid input spec version
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidSpecVersion(String specVersion) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_SPEC_VERSION,
|
||||
|
@ -94,6 +103,10 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidAttributeName(String attributeName) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_NAME,
|
||||
|
@ -101,6 +114,10 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param extensionName the invalid extension name
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidExtensionName(String extensionName) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_EXTENSION_NAME,
|
||||
|
@ -108,6 +125,11 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @param clazz the type of the attribute
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidAttributeType(String attributeName, Class<?> clazz) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
|
@ -115,7 +137,24 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newInvalidAttributeValue(String attributeName, Object value, Throwable cause) {
|
||||
public static CloudEventRWException newInvalidAttributeType(String attributeName,Object value){
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
"Invalid attribute/extension type for \""
|
||||
+ attributeName
|
||||
+ "\": Type" + value.getClass().getCanonicalName()
|
||||
+ ". Value: " + value
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @param value the value of the attribute
|
||||
* @param cause an optional cause identifying the eventual validation error
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidAttributeValue(String attributeName, Object value, @Nullable Throwable cause) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_VALUE,
|
||||
"Invalid attribute/extension value for \"" + attributeName + "\": " + value,
|
||||
|
@ -123,6 +162,11 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param actual the actual data type
|
||||
* @param allowed the list of allowed data types
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidDataType(String actual, String... allowed) {
|
||||
String message;
|
||||
if (allowed.length == 0) {
|
||||
|
@ -136,6 +180,12 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cause the cause of the conversion failure
|
||||
* @param from the input data type
|
||||
* @param to the target data type
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newDataConversion(Throwable cause, String from, String to) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.DATA_CONVERSION,
|
||||
|
@ -144,6 +194,22 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new {@link CloudEventRWException} instance.
|
||||
*/
|
||||
public static CloudEventRWException newUnknownEncodingException() {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.UNKNOWN_ENCODING,
|
||||
"Could not parse. Unknown encoding. Invalid content type or spec version"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This wraps a {@link Throwable} in a new generic instance of this exception.
|
||||
*
|
||||
* @param cause the cause of the exception
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newOther(Throwable cause) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.OTHER,
|
||||
|
@ -151,10 +217,14 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newUnknownEncodingException() {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.UNKNOWN_ENCODING,
|
||||
"Could not parse. Unknown encoding. Invalid content type or spec version"
|
||||
);
|
||||
/**
|
||||
* An exception for use where none of the other variants are
|
||||
* appropriate.
|
||||
*
|
||||
* @param msg A description error message.
|
||||
* @return a new {@link CloudEventRWException}
|
||||
*/
|
||||
public static CloudEventRWException newOther(String msg){
|
||||
return new CloudEventRWException(CloudEventRWExceptionKind.OTHER, msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,18 +30,29 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
public interface CloudEventReader {
|
||||
|
||||
/**
|
||||
* Visit self using the provided visitor factory
|
||||
* Like {@link #read(CloudEventWriterFactory, CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @param <W> The type of the {@link CloudEventWriter} created by writerFactory
|
||||
* @param <R> The return value of the {@link CloudEventWriter} created by writerFactory
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @see #read(CloudEventWriterFactory, CloudEventDataMapper)
|
||||
* @return the value returned by {@link CloudEventWriter#end()} or {@link CloudEventWriter#end(CloudEventData)}
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
*/
|
||||
default <V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory) throws CloudEventRWException {
|
||||
default <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException {
|
||||
return read(writerFactory, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link CloudEventReader#read(CloudEventWriterFactory)}, but providing a mapper for {@link io.cloudevents.CloudEventData} to be invoked when the data field is available.
|
||||
* Read self using the provided writer factory.
|
||||
*
|
||||
* @param <W> the {@link CloudEventWriter} type
|
||||
* @param <R> the return type of the {@link CloudEventWriter}
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @param mapper the mapper to invoke when building the {@link CloudEventData}
|
||||
* @return the value returned by {@link CloudEventWriter#end()} or {@link CloudEventWriter#end(CloudEventData)}
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
*/
|
||||
<V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException;
|
||||
<W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException;
|
||||
|
||||
}
|
||||
|
|
|
@ -25,18 +25,19 @@ import io.cloudevents.CloudEventData;
|
|||
*
|
||||
* @param <R> return value at the end of the write process
|
||||
*/
|
||||
public interface CloudEventWriter<R> extends CloudEventAttributesWriter, CloudEventExtensionsWriter {
|
||||
public interface CloudEventWriter<R> extends CloudEventContextWriter {
|
||||
|
||||
/**
|
||||
* End the visit with a data field
|
||||
* End the write with a data payload.
|
||||
*
|
||||
* @param data the data to write
|
||||
* @return an eventual return value
|
||||
* @throws CloudEventRWException if the message writer cannot be ended.
|
||||
*/
|
||||
R end(CloudEventData data) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* End the visit
|
||||
* End the write.
|
||||
*
|
||||
* @return an eventual return value
|
||||
* @throws CloudEventRWException if the message writer cannot be ended.
|
||||
|
|
|
@ -19,8 +19,14 @@ package io.cloudevents.rw;
|
|||
|
||||
import io.cloudevents.SpecVersion;
|
||||
|
||||
/**
|
||||
* This factory is used to enforce setting the {@link SpecVersion} as first step in the writing process.
|
||||
*
|
||||
* @param <W> The type of the {@link CloudEventWriter} created by this factory
|
||||
* @param <R> The return value of the {@link CloudEventWriter} created by this factory
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CloudEventWriterFactory<V extends CloudEventWriter<R>, R> {
|
||||
public interface CloudEventWriterFactory<W extends CloudEventWriter<R>, R> {
|
||||
|
||||
/**
|
||||
* Create a {@link CloudEventWriter} starting from the provided {@link SpecVersion}
|
||||
|
@ -29,5 +35,5 @@ public interface CloudEventWriterFactory<V extends CloudEventWriter<R>, R> {
|
|||
* @return the new writer
|
||||
* @throws CloudEventRWException if the spec version is invalid or the writer cannot be instantiated.
|
||||
*/
|
||||
V create(SpecVersion version) throws CloudEventRWException;
|
||||
W create(SpecVersion version) throws CloudEventRWException;
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-benchmarks</artifactId>
|
||||
|
@ -34,6 +34,7 @@
|
|||
<javac.target>1.8</javac.target>
|
||||
<!-- Name of the benchmark Uber-JAR to generate. -->
|
||||
<uberjar.name>benchmarks</uberjar.name>
|
||||
<maven.javadoc.skip>true</maven.javadoc.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
@ -58,6 +59,11 @@
|
|||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-sql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
|
@ -135,7 +141,7 @@
|
|||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -155,7 +161,7 @@
|
|||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package io.cloudevents.bench.sql;
|
||||
|
||||
import io.cloudevents.sql.Parser;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
public class CompileBenchmark {
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(a + b + c) = 10 AND TRUE OR CONCAT('1', '2', id) = '123'")
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallTrueConstantExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(1 + 2 + 3) = 10 AND TRUE OR CONCAT('1', '2', '3') = 123")
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallFalseConstantExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(1 + 2 + 3) = 10 AND FALSE OR CONCAT('1', '2', '3') = 124")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.cloudevents.bench.sql;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.sql.Expression;
|
||||
import io.cloudevents.sql.Parser;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_JSON_DATA_WITH_EXT;
|
||||
|
||||
public class RunBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("a", "10")
|
||||
.withExtension("b", 3)
|
||||
.withExtension("c", "-3")
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(a + b + c) = 10 AND TRUE OR CONCAT('1', '2', id) = '123'");
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallTrueConstantExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(1 + 2 + 3) = 10 AND TRUE OR CONCAT('1', '2', '3') = 123");
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallFalseConstantExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(1 + 2 + 3) = 10 AND FALSE OR CONCAT('1', '2', '3') = 124");
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallExpression(TestCaseSmallExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallExpression(TestCaseSmallExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallTrueConstantExpression(TestCaseSmallTrueConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallTrueConstantExpression(TestCaseSmallTrueConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallFalseConstantExpression(TestCaseSmallFalseConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallFalseConstantExpression(TestCaseSmallFalseConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2018-Present The CloudEvents Authors
|
||||
~ <p>
|
||||
~ 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
|
||||
~ <p>
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~ <p>
|
||||
~ 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.
|
||||
~
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-bom</artifactId>
|
||||
<name>CloudEvents - Bill of Materials</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-json-jackson</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-protobuf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-basic</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-vertx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-spring</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-sql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
|
@ -1,67 +1,5 @@
|
|||
# CloudEvents Core
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
Javadoc: [](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
|
||||
The base classes, interfaces and low-level APIs to use CloudEvents.
|
||||
|
||||
## How to Use
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>2.0.0.RC1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Create an Event
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import java.net.URI;
|
||||
|
||||
final CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("000")
|
||||
.withType("example.demo")
|
||||
.withSource(URI.create("http://example.com"))
|
||||
.withData("application/json", "{}".getBytes())
|
||||
.build();
|
||||
```
|
||||
|
||||
### Materialize an Extension
|
||||
|
||||
CloudEvent extensions can be materialized in their respective POJOs:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.core.extensions.ExtensionsParser;
|
||||
|
||||
DistributedTracingExtension dte = ExtensionsParser.getInstance()
|
||||
.parseExtension(DistributedTracingExtension.class, event);
|
||||
```
|
||||
|
||||
### Using Event Formats
|
||||
|
||||
The SDK implements [Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format) in submodules.
|
||||
To use them, you just need to add them as dependencies to your project and the SDK,
|
||||
through the `ServiceLoader` mechanism, will load them into the classpath.
|
||||
For example, to use the [JSON event format](https://github.com/cloudevents/spec/blob/v1.0/json-format.md) with Jackson,
|
||||
add `cloudevents-json-jackson` as a dependency and then:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.format.EventFormatProvider;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
|
||||
EventFormat format = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(JsonFormat.CONTENT_TYPE);
|
||||
|
||||
// Serialize event
|
||||
byte[] serialized = format.serialize(event);
|
||||
|
||||
// Deserialize event
|
||||
CloudEvent event = format.deserialize(bytes);
|
||||
```
|
||||
Documentation: https://cloudevents.github.io/sdk-java/core
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0-SNAPSHOT</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
|
|
|
@ -30,7 +30,7 @@ import io.cloudevents.rw.CloudEventRWException;
|
|||
import io.cloudevents.rw.CloudEventReader;
|
||||
|
||||
/**
|
||||
* This class contains a set of utility methods to deal with conversions of io.cloudevents related interfaces
|
||||
* This class contains a set of utility methods to deal with conversions of {@link io.cloudevents} related interfaces
|
||||
*/
|
||||
public final class CloudEventUtils {
|
||||
|
||||
|
@ -38,8 +38,7 @@ public final class CloudEventUtils {
|
|||
|
||||
/**
|
||||
* Convert a {@link CloudEvent} to a {@link CloudEventReader}. This method provides a default implementation
|
||||
* for CloudEvent that doesn't implement {@link CloudEventReader}
|
||||
* for CloudEvent that doesn't implement CloudEventVisitable.
|
||||
* for CloudEvent that doesn't implement {@link CloudEventReader}.
|
||||
* <p>
|
||||
* It's safe to use the returned {@link CloudEventReader} multiple times.
|
||||
*
|
||||
|
@ -94,6 +93,11 @@ public final class CloudEventUtils {
|
|||
|
||||
/**
|
||||
* Get the data contained in {@code event} and map it using the provided mapper.
|
||||
*
|
||||
* @param event the event eventually containing the data
|
||||
* @param mapper the mapper to use to map the data
|
||||
* @param <R> the returned {@link CloudEventData} implementation from the provided mapper
|
||||
* @return the data contained in {@code event} and mapped with {@code mapper}, if any, otherwise null
|
||||
*/
|
||||
@Nullable
|
||||
public static <R extends CloudEventData> R mapData(CloudEvent event, CloudEventDataMapper<R> mapper) {
|
||||
|
|
|
@ -17,18 +17,13 @@
|
|||
|
||||
package io.cloudevents.core.builder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import io.cloudevents.*;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.ParametersAreNullableByDefault;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* Builder interface to build a {@link CloudEvent}.
|
||||
|
@ -146,6 +141,28 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
*/
|
||||
CloudEventBuilder withData(String dataContentType, URI dataSchema, CloudEventData data);
|
||||
|
||||
/**
|
||||
* Remove the {@code datacontenttype}, {@code dataschema} and {@code data} from the event
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutData();
|
||||
|
||||
/**
|
||||
* Remove the {@code dataschema} from the event
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutDataSchema();
|
||||
|
||||
|
||||
/**
|
||||
* Remove the {@code datacontenttype} from the event
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutDataContentType();
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and string value
|
||||
*
|
||||
|
@ -153,7 +170,6 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
@Override
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull String value);
|
||||
|
||||
/**
|
||||
|
@ -163,7 +179,6 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
@Override
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull Number value);
|
||||
|
||||
/**
|
||||
|
@ -173,16 +188,42 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
@Override
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull Boolean value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and uri value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull URI value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and boolean value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull OffsetDateTime value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and binary value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull byte[] value);
|
||||
|
||||
/**
|
||||
* Add to the builder all the extension key/values of the provided extension
|
||||
*
|
||||
* @param extension materialized extension to set in the builder
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull Extension extension);
|
||||
CloudEventBuilder withExtension(@Nonnull CloudEventExtension extension);
|
||||
|
||||
/**
|
||||
* Remove from the the builder the provided extension key, if any
|
||||
|
@ -198,7 +239,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param extension materialized extension to remove from the builder
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutExtension(@Nonnull Extension extension);
|
||||
CloudEventBuilder withoutExtension(@Nonnull CloudEventExtension extension);
|
||||
|
||||
/**
|
||||
* Build the event
|
||||
|
@ -290,9 +331,9 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
static CloudEventBuilder fromContext(@Nonnull CloudEventContext context) {
|
||||
switch (context.getSpecVersion()) {
|
||||
case V1:
|
||||
return new io.cloudevents.core.v1.CloudEventBuilder(context);
|
||||
return new io.cloudevents.core.v1.CloudEventBuilder(context);
|
||||
case V03:
|
||||
return new io.cloudevents.core.v03.CloudEventBuilder(context);
|
||||
return new io.cloudevents.core.v03.CloudEventBuilder(context);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"The provided spec version doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
|
|
|
@ -5,11 +5,15 @@ import io.cloudevents.CloudEventData;
|
|||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of {@link CloudEventData} that wraps a byte array.
|
||||
*/
|
||||
public class BytesCloudEventData implements CloudEventData {
|
||||
|
||||
private final byte[] value;
|
||||
|
||||
/**
|
||||
* @param value the bytes to wrap
|
||||
* @deprecated use {@link BytesCloudEventData#wrap(byte[])}
|
||||
*/
|
||||
public BytesCloudEventData(byte[] value) {
|
||||
|
|
|
@ -5,6 +5,11 @@ import io.cloudevents.rw.CloudEventRWException;
|
|||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of {@link CloudEventData} that wraps any POJO.
|
||||
*
|
||||
* @param <T> the type of the wrapped POJO.
|
||||
*/
|
||||
public class PojoCloudEventData<T> implements CloudEventData {
|
||||
|
||||
/**
|
||||
|
@ -15,6 +20,11 @@ public class PojoCloudEventData<T> implements CloudEventData {
|
|||
*/
|
||||
@FunctionalInterface
|
||||
public interface ToBytes<T> {
|
||||
/**
|
||||
* @param data the POJO to convert
|
||||
* @return the serialized byte array.
|
||||
* @throws Exception when something goes wrong during the conversion.
|
||||
*/
|
||||
byte[] convert(T data) throws Exception;
|
||||
}
|
||||
|
||||
|
@ -36,6 +46,9 @@ public class PojoCloudEventData<T> implements CloudEventData {
|
|||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the wrapped POJO
|
||||
*/
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
@ -67,6 +80,11 @@ public class PojoCloudEventData<T> implements CloudEventData {
|
|||
|
||||
/**
|
||||
* Wrap the provided data in a {@link PojoCloudEventData} serializable by the provided mapper.
|
||||
*
|
||||
* @param <T> The type of {@code data}
|
||||
* @param data the POJO to wrap
|
||||
* @param mapper converter from {@code data} to bytes, used to implement {@link #toBytes()}
|
||||
* @return the new {@link PojoCloudEventData}
|
||||
*/
|
||||
public static <T> PojoCloudEventData<T> wrap(T data, ToBytes<T> mapper) {
|
||||
return new PojoCloudEventData<>(data, mapper);
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
package io.cloudevents.core.extensions;
|
||||
|
||||
import io.cloudevents.CloudEventExtensions;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.extensions.impl.ExtensionUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -27,19 +28,30 @@ import java.util.Set;
|
|||
|
||||
/**
|
||||
* This extension supports the "Claim Check Pattern". It allows to specify a reference to a location where the event payload is stored.
|
||||
*
|
||||
* @see <a href=https://github.com/cloudevents/spec/blob/v1.0/extensions/dataref.md>https://github.com/cloudevents/spec/blob/v1.0/extensions/dataref.md</a>
|
||||
*/
|
||||
public final class DatarefExtension implements Extension {
|
||||
public final class DatarefExtension implements CloudEventExtension {
|
||||
|
||||
/**
|
||||
* The key of the {@code dataref} extension
|
||||
*/
|
||||
public static final String DATAREF = "dataref";
|
||||
|
||||
private static final Set<String> KEY_SET = Collections.unmodifiableSet(new HashSet<>(Collections.singletonList(DATAREF)));
|
||||
|
||||
private URI dataref;
|
||||
|
||||
/**
|
||||
* @return the {@code dataref} contained in this extension.
|
||||
*/
|
||||
public URI getDataref() {
|
||||
return dataref;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dataref the uri to set as {@code dataref}.
|
||||
*/
|
||||
public void setDataref(URI dataref) {
|
||||
this.dataref = dataref;
|
||||
}
|
||||
|
@ -57,7 +69,7 @@ public final class DatarefExtension implements Extension {
|
|||
if (DATAREF.equals(key)) {
|
||||
return this.dataref.toString();
|
||||
}
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass().getSimpleName(), key);
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,40 +18,57 @@
|
|||
package io.cloudevents.core.extensions;
|
||||
|
||||
import io.cloudevents.CloudEventExtensions;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.extensions.impl.ExtensionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This extension embeds context from Distributed Tracing so that distributed systems can include traces that span an event-driven system.
|
||||
*
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md">https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md</a>
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/main/extensions/distributed-tracing.md">https://github.com/cloudevents/spec/blob/main/extensions/distributed-tracing.md</a>
|
||||
*/
|
||||
public final class DistributedTracingExtension implements Extension {
|
||||
public final class DistributedTracingExtension implements CloudEventExtension {
|
||||
|
||||
/**
|
||||
* The key of the {@code traceparent} extension
|
||||
*/
|
||||
public static final String TRACEPARENT = "traceparent";
|
||||
|
||||
/**
|
||||
* The key of the {@code tracestate} extension
|
||||
*/
|
||||
public static final String TRACESTATE = "tracestate";
|
||||
|
||||
private static final Set<String> KEY_SET = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(TRACEPARENT, TRACESTATE)));
|
||||
|
||||
private String traceparent;
|
||||
private String tracestate;
|
||||
|
||||
/**
|
||||
* @return the {@code traceparent} contained in this extension.
|
||||
*/
|
||||
public String getTraceparent() {
|
||||
return traceparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param traceparent the string to set as {@code traceparent}.
|
||||
*/
|
||||
public void setTraceparent(String traceparent) {
|
||||
this.traceparent = traceparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@code tracestate} contained in this extension.
|
||||
*/
|
||||
public String getTracestate() {
|
||||
return tracestate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tracestate the string to set as {@code tracestate}.
|
||||
*/
|
||||
public void setTracestate(String tracestate) {
|
||||
this.tracestate = tracestate;
|
||||
}
|
||||
|
@ -76,7 +93,7 @@ public final class DistributedTracingExtension implements Extension {
|
|||
case TRACESTATE:
|
||||
return this.tracestate;
|
||||
}
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass().getSimpleName(), key);
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -93,35 +110,16 @@ public final class DistributedTracingExtension implements Extension {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((traceparent == null) ? 0
|
||||
: traceparent.hashCode());
|
||||
result = prime * result + ((tracestate == null) ? 0
|
||||
: tracestate.hashCode());
|
||||
return result;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DistributedTracingExtension that = (DistributedTracingExtension) o;
|
||||
return Objects.equals(getTraceparent(), that.getTraceparent()) &&
|
||||
Objects.equals(getTracestate(), that.getTracestate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
DistributedTracingExtension other = (DistributedTracingExtension) obj;
|
||||
if (traceparent == null) {
|
||||
if (other.traceparent != null)
|
||||
return false;
|
||||
} else if (!traceparent.equals(other.traceparent))
|
||||
return false;
|
||||
if (tracestate == null) {
|
||||
if (other.tracestate != null)
|
||||
return false;
|
||||
} else if (!tracestate.equals(other.tracestate))
|
||||
return false;
|
||||
return true;
|
||||
public int hashCode() {
|
||||
return Objects.hash(getTraceparent(), getTracestate());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,23 @@
|
|||
|
||||
package io.cloudevents.core.extensions.impl;
|
||||
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
|
||||
/**
|
||||
* Collection of utilities to deal with materialized extensions
|
||||
*/
|
||||
public final class ExtensionUtils {
|
||||
|
||||
private ExtensionUtils() {
|
||||
}
|
||||
|
||||
public static IllegalArgumentException generateInvalidKeyException(String extensionName, String key) {
|
||||
return new IllegalArgumentException(extensionName + " doesn't expect the attribute key \"" + key + "\"");
|
||||
/**
|
||||
* @param clazz the {@link CloudEventExtension} class
|
||||
* @param key the invalid key
|
||||
* @return an {@link IllegalArgumentException} when trying to access a key of the extension not existing.
|
||||
*/
|
||||
public static IllegalArgumentException generateInvalidKeyException(Class<? extends CloudEventExtension> clazz, String key) {
|
||||
return new IllegalArgumentException(clazz.getName() + " doesn't expect the attribute key \"" + key + "\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.format;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>A construct that aggregates a two-part identifier of file formats and format contents transmitted on the Internet.
|
||||
*
|
||||
* <p>The two parts of a {@code ContentType} are its <em>type</em> and a <em>subtype</em>; separated by a forward slash ({@code /}).
|
||||
*
|
||||
* <p>The constants enumerated by {@code ContentType} correspond <em>only</em> to the specialized formats supported by the Java™ SDK for CloudEvents.
|
||||
*
|
||||
* @see io.cloudevents.core.format.EventFormat
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public enum ContentType {
|
||||
|
||||
/**
|
||||
* Content type associated with the JSON event format
|
||||
*/
|
||||
JSON("application/cloudevents+json"),
|
||||
/**
|
||||
* The content type for transports sending cloudevents in the protocol buffer format.
|
||||
*/
|
||||
PROTO("application/cloudevents+protobuf"),
|
||||
/**
|
||||
* The content type for transports sending cloudevents in the compact Avro format.
|
||||
*/
|
||||
AVRO_COMPACT("application/cloudevents+avrocompact"),
|
||||
/**
|
||||
* The content type for transports sending cloudevents in XML format.
|
||||
*/
|
||||
XML("application/cloudevents+xml");
|
||||
|
||||
private String value;
|
||||
|
||||
private ContentType(String value) { this.value = value; }
|
||||
|
||||
/**
|
||||
* Return a string consisting of the slash-delimited ({@code /}) two-part identifier for this {@code enum} constant.
|
||||
*/
|
||||
public String value() { return value; }
|
||||
|
||||
/**
|
||||
* Return a string consisting of the slash-delimited ({@code /}) two-part identifier for this {@code enum} constant.
|
||||
*/
|
||||
@Override
|
||||
public String toString() { return value(); }
|
||||
|
||||
}
|
|
@ -21,7 +21,10 @@ package io.cloudevents.core.format;
|
|||
* Exception representing a deserialization error while using an {@link EventFormat}.
|
||||
*/
|
||||
public class EventDeserializationException extends RuntimeException {
|
||||
public EventDeserializationException(Throwable e) {
|
||||
super(e);
|
||||
/**
|
||||
* @param cause the cause of the exception
|
||||
*/
|
||||
public EventDeserializationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,18 +48,21 @@ public interface EventFormat {
|
|||
byte[] serialize(CloudEvent event) throws EventSerializationException;
|
||||
|
||||
/**
|
||||
* Deserialize a byte array to a {@link CloudEvent}.
|
||||
* Like {@link #deserialize(byte[], CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @param bytes the serialized event.
|
||||
* @return the deserialized event.
|
||||
* @throws EventDeserializationException if something goes wrong during deserialization.
|
||||
* @see #deserialize(byte[], CloudEventDataMapper)
|
||||
*/
|
||||
default CloudEvent deserialize(byte[] bytes) throws EventDeserializationException {
|
||||
return this.deserialize(bytes, null);
|
||||
return this.deserialize(bytes, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link EventFormat#deserialize(byte[])}, but allows a mapper that maps the parsed {@link io.cloudevents.CloudEventData} to another one.
|
||||
* Deserialize a byte array to a {@link CloudEvent}.
|
||||
*
|
||||
* @param bytes the serialized event.
|
||||
* @param mapper the mapper to use to map the data.
|
||||
* @return the deserialized event.
|
||||
* @throws EventDeserializationException if something goes wrong during deserialization.
|
||||
*/
|
||||
CloudEvent deserialize(byte[] bytes, CloudEventDataMapper<? extends CloudEventData> mapper) throws EventDeserializationException;
|
||||
|
||||
|
|
|
@ -21,7 +21,10 @@ package io.cloudevents.core.format;
|
|||
* Exception representing a serialization error while using an {@link EventFormat}.
|
||||
*/
|
||||
public class EventSerializationException extends RuntimeException {
|
||||
public EventSerializationException(Throwable e) {
|
||||
super(e);
|
||||
/**
|
||||
* @param cause the cause of the exception
|
||||
*/
|
||||
public EventSerializationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import io.cloudevents.CloudEvent;
|
|||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.rw.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
@ -52,26 +54,31 @@ public abstract class BaseCloudEvent implements CloudEvent, CloudEventReader, Cl
|
|||
|
||||
@Override
|
||||
public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException {
|
||||
CloudEventWriter<V> visitor = writerFactory.create(this.getSpecVersion());
|
||||
this.readAttributes(visitor);
|
||||
this.readExtensions(visitor);
|
||||
CloudEventWriter<V> writer = writerFactory.create(this.getSpecVersion());
|
||||
this.readContext(writer);
|
||||
|
||||
if (this.data != null) {
|
||||
return visitor.end(mapper.map(this.data));
|
||||
return writer.end(mapper.map(this.data));
|
||||
}
|
||||
|
||||
return visitor.end();
|
||||
return writer.end();
|
||||
}
|
||||
|
||||
public void readExtensions(CloudEventExtensionsWriter writer) throws CloudEventRWException {
|
||||
protected void readExtensions(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
// TODO to be improved
|
||||
for (Map.Entry<String, Object> entry : this.extensions.entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
writer.withExtension(entry.getKey(), (String) entry.getValue());
|
||||
writer.withContextAttribute(entry.getKey(), (String) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Number) {
|
||||
writer.withExtension(entry.getKey(), (Number) entry.getValue());
|
||||
writer.withContextAttribute(entry.getKey(), (Number) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Boolean) {
|
||||
writer.withExtension(entry.getKey(), (Boolean) entry.getValue());
|
||||
writer.withContextAttribute(entry.getKey(), (Boolean) entry.getValue());
|
||||
} else if (entry.getValue() instanceof URI) {
|
||||
writer.withContextAttribute(entry.getKey(), (URI) entry.getValue());
|
||||
} else if (entry.getValue() instanceof OffsetDateTime) {
|
||||
writer.withContextAttribute(entry.getKey(), (OffsetDateTime) entry.getValue());
|
||||
} else if (entry.getValue() instanceof byte[]) {
|
||||
writer.withContextAttribute(entry.getKey(), (byte[]) entry.getValue());
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
|
||||
|
|
|
@ -17,20 +17,21 @@
|
|||
|
||||
package io.cloudevents.core.impl;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.data.BytesCloudEventData;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.data.BytesCloudEventData;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import static io.cloudevents.core.v03.CloudEventV03.SPECVERSION;
|
||||
|
||||
public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<SELF, T>, T extends CloudEvent> implements CloudEventBuilder {
|
||||
|
||||
|
@ -97,6 +98,24 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withoutData() {
|
||||
this.data = null;
|
||||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withoutDataSchema() {
|
||||
withDataSchema(null);
|
||||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withoutDataContentType() {
|
||||
withDataContentType(null);
|
||||
return this.self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull String value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
|
@ -105,6 +124,9 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return self;
|
||||
}
|
||||
|
||||
// @TODO - I think this method should be removed/deprecated
|
||||
// **Number** Is NOT a valid CE Context atrribute type.
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Number value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
|
@ -113,6 +135,14 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Integer value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Boolean value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
|
@ -121,6 +151,33 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull URI value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull OffsetDateTime value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withExtension(@Nonnull String key, @Nonnull byte[] value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withoutExtension(@Nonnull String key) {
|
||||
this.extensions.remove(key);
|
||||
|
@ -128,12 +185,12 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
}
|
||||
|
||||
@Override
|
||||
public SELF withoutExtension(@Nonnull Extension extension) {
|
||||
public SELF withoutExtension(@Nonnull CloudEventExtension extension) {
|
||||
extension.getKeys().forEach(this::withoutExtension);
|
||||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull Extension extension) {
|
||||
public SELF withExtension(@Nonnull CloudEventExtension extension) {
|
||||
for (String key : extension.getKeys()) {
|
||||
Object value = extension.getValue(key);
|
||||
if (value != null) {
|
||||
|
@ -161,20 +218,23 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
protected static IllegalStateException createMissingAttributeException(String attributeName) {
|
||||
return new IllegalStateException("Attribute '" + attributeName + "' cannot be null");
|
||||
}
|
||||
|
||||
protected static IllegalStateException createEmptyAttributeException(String attributeName) {
|
||||
return new IllegalStateException("Attribute '" + attributeName + "' cannot be empty");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the extension name as defined in CloudEvents spec
|
||||
* See <a href="https://github.com/cloudevents/spec/blob/master/spec.md#attribute-naming-convention">attribute-naming-convention</a>
|
||||
* Validates the extension name as defined in CloudEvents spec.
|
||||
*
|
||||
* @param name the extension name
|
||||
* @return true if extension name is valid, false otherwise
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/main/cloudevents/spec.md#naming-conventions">attribute-naming-conventions</a>
|
||||
*/
|
||||
private static boolean isValidExtensionName(String name) {
|
||||
if(name.length() > 20){
|
||||
return false;
|
||||
}
|
||||
char[] chars = name.toCharArray();
|
||||
for (char c: chars)
|
||||
if (!isValidChar(c)) {
|
||||
return false;
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
if (!isValidChar(name.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -182,4 +242,10 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
private static boolean isValidChar(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
protected void requireValidAttributeWrite(String name) {
|
||||
if (name.equals(SPECVERSION)) {
|
||||
throw new IllegalArgumentException("You should not set the specversion attribute through withContextAttribute methods");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,9 +18,12 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
import io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventExtensionsWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class CloudEventContextReaderAdapter implements CloudEventContextReader {
|
||||
|
||||
|
@ -30,41 +33,47 @@ public class CloudEventContextReaderAdapter implements CloudEventContextReader {
|
|||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws RuntimeException {
|
||||
writer.withAttribute("id", event.getId());
|
||||
writer.withAttribute("source", event.getSource());
|
||||
writer.withAttribute("type", event.getType());
|
||||
public void readAttributes(CloudEventContextWriter writer) throws RuntimeException {
|
||||
writer.withContextAttribute("id", event.getId());
|
||||
writer.withContextAttribute("source", event.getSource());
|
||||
writer.withContextAttribute("type", event.getType());
|
||||
if (event.getDataContentType() != null) {
|
||||
writer.withAttribute("datacontenttype", event.getDataContentType());
|
||||
writer.withContextAttribute("datacontenttype", event.getDataContentType());
|
||||
}
|
||||
if (event.getDataSchema() != null) {
|
||||
writer.withAttribute("dataschema", event.getDataSchema());
|
||||
writer.withContextAttribute("dataschema", event.getDataSchema());
|
||||
}
|
||||
if (event.getSubject() != null) {
|
||||
writer.withAttribute("subject", event.getSubject());
|
||||
writer.withContextAttribute("subject", event.getSubject());
|
||||
}
|
||||
if (event.getTime() != null) {
|
||||
writer.withAttribute("time", event.getTime());
|
||||
writer.withContextAttribute("time", event.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtensions(CloudEventExtensionsWriter writer) throws RuntimeException {
|
||||
public void readExtensions(CloudEventContextWriter writer) throws RuntimeException {
|
||||
for (String key : event.getExtensionNames()) {
|
||||
Object value = event.getExtension(key);
|
||||
if (value instanceof String) {
|
||||
writer.withExtension(key, (String) value);
|
||||
writer.withContextAttribute(key, (String) value);
|
||||
} else if (value instanceof Number) {
|
||||
writer.withExtension(key, (Number) value);
|
||||
writer.withContextAttribute(key, (Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
writer.withExtension(key, (Boolean) value);
|
||||
writer.withContextAttribute(key, (Boolean) value);
|
||||
} else if (value instanceof URI) {
|
||||
writer.withContextAttribute(key, (URI) value);
|
||||
} else if (value instanceof OffsetDateTime) {
|
||||
writer.withContextAttribute(key, (OffsetDateTime) value);
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + key + " " + value);
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
this.readAttributes(writer);
|
||||
this.readExtensions(writer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.impl;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
final public class StringUtils {
|
||||
|
||||
private StringUtils() {
|
||||
// Prevent construction.
|
||||
}
|
||||
|
||||
public static boolean startsWithIgnoreCase(@Nonnull final String s, @Nonnull final String prefix) {
|
||||
return s.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
|
||||
}
|
||||
}
|
|
@ -21,6 +21,12 @@ package io.cloudevents.core.message;
|
|||
* One of the possible encodings of a <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#message">CloudEvent message</a>
|
||||
*/
|
||||
public enum Encoding {
|
||||
/**
|
||||
* Structured mode
|
||||
*/
|
||||
STRUCTURED,
|
||||
/**
|
||||
* Binary mode
|
||||
*/
|
||||
BINARY
|
||||
}
|
||||
|
|
|
@ -19,41 +19,50 @@ package io.cloudevents.core.message;
|
|||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.rw.*;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* Represents a <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#message">CloudEvent message</a>.
|
||||
* Represents a <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#message">CloudEvent message</a> reader.
|
||||
* <p>
|
||||
* This class expands the {@link CloudEventReader} to define reading both binary and structured messages.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface MessageReader extends StructuredMessageReader, CloudEventReader {
|
||||
|
||||
/**
|
||||
* Visit the message as binary encoded event using the provided visitor factory.
|
||||
* Like {@link #read(CloudEventWriterFactory, CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in binary encoding.
|
||||
* @see #read(CloudEventWriterFactory, CloudEventDataMapper)
|
||||
*/
|
||||
default <V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory) throws CloudEventRWException, IllegalStateException {
|
||||
default <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException, IllegalStateException {
|
||||
return read(writerFactory, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Like {@link MessageReader#read(CloudEventWriterFactory)}, but providing a mapper for {@link io.cloudevents.CloudEventData} to be invoked when the data field is available.
|
||||
* Read the message as binary encoded message using the provided writer factory.
|
||||
*
|
||||
* @param <W> the {@link CloudEventWriter} type
|
||||
* @param <R> the return type of the {@link CloudEventWriter}
|
||||
* @param writerFactory a factory that generates a reader starting from the {@link SpecVersion} of the event
|
||||
* @param mapper the mapper to use to map the data, if any.
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in binary encoding.
|
||||
*/
|
||||
<V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException;
|
||||
<W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* Visit the message as structured encoded event using the provided visitor
|
||||
* Read the message as structured encoded message using the provided writer
|
||||
*
|
||||
* @param visitor Structured Message visitor
|
||||
* @param <R> the return type of the {@link StructuredMessageWriter}
|
||||
* @param writer Structured Message writer
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in structured encoding.
|
||||
* @throws IllegalStateException if the message is not in structured encoding.
|
||||
*/
|
||||
<T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException;
|
||||
<R> R read(StructuredMessageWriter<R> writer) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* @return The message encoding
|
||||
|
@ -64,38 +73,41 @@ public interface MessageReader extends StructuredMessageReader, CloudEventReader
|
|||
* Read the content of this object using a {@link MessageWriter}. This method allows to transcode an event from one transport to another without
|
||||
* converting it to {@link CloudEvent}. The resulting encoding will be the same as the original encoding.
|
||||
*
|
||||
* @param visitor the MessageVisitor accepting this Message
|
||||
* @return The return value of the MessageVisitor
|
||||
* @param <BW> the {@link CloudEventWriter} type
|
||||
* @param <R> the return type of both {@link CloudEventWriter} and {@link StructuredMessageWriter}
|
||||
* @param writer the {@link MessageWriter} accepting this Message
|
||||
* @return The return value of the {@link MessageWriter}
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
*/
|
||||
default <BV extends CloudEventWriter<R>, R> R read(MessageWriter<BV, R> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
default <BW extends CloudEventWriter<R>, R> R read(MessageWriter<BW, R> writer) throws CloudEventRWException, IllegalStateException {
|
||||
switch (getEncoding()) {
|
||||
case BINARY:
|
||||
return this.read((CloudEventWriterFactory<BV, R>) visitor);
|
||||
return this.read((CloudEventWriterFactory<BW, R>) writer);
|
||||
case STRUCTURED:
|
||||
return this.read((StructuredMessageWriter<R>) visitor);
|
||||
return this.read((StructuredMessageWriter<R>) writer);
|
||||
default:
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
throw new IllegalStateException(
|
||||
"The provided Encoding doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate this message into a {@link CloudEvent} representation.
|
||||
* Like {@link #toEvent(CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @return A {@link CloudEvent} with the contents of this message.
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
* @see #toEvent(CloudEventDataMapper)
|
||||
*/
|
||||
default CloudEvent toEvent() throws CloudEventRWException, IllegalStateException {
|
||||
return toEvent(CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate this message into a {@link CloudEvent} representation.
|
||||
* Translate this message into a {@link CloudEvent} representation, mapping the data with the provided {@code mapper}.
|
||||
*
|
||||
* @param mapper the mapper to use to map the data, if any.
|
||||
* @return A {@link CloudEvent} with the contents of this message.
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
*/
|
||||
default CloudEvent toEvent(CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException {
|
||||
|
@ -105,7 +117,9 @@ public interface MessageReader extends StructuredMessageReader, CloudEventReader
|
|||
case STRUCTURED:
|
||||
return this.read((format, value) -> format.deserialize(value, mapper));
|
||||
default:
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
throw new IllegalStateException(
|
||||
"The provided Encoding doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,11 +34,15 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
public interface StructuredMessageReader {
|
||||
|
||||
/**
|
||||
* @param visitor
|
||||
* Read self using the provided writer.
|
||||
*
|
||||
* @param <R> the return type of the {@link StructuredMessageWriter}
|
||||
* @param writer the writer to use to write out the message
|
||||
* @return the return value returned by {@link StructuredMessageWriter#setEvent(EventFormat, byte[])}
|
||||
* @throws CloudEventRWException If something went wrong when
|
||||
* @throws IllegalStateException If the message is not a valid structured message
|
||||
*/
|
||||
<T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException;
|
||||
<R> R read(StructuredMessageWriter<R> writer) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
default CloudEvent toEvent() throws CloudEventRWException, IllegalStateException {
|
||||
return this.read(EventFormat::deserialize);
|
||||
|
@ -49,9 +53,9 @@ public interface StructuredMessageReader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a generic structured message from a {@link CloudEvent}
|
||||
* Create a generic structured message from a {@link CloudEvent}.
|
||||
*
|
||||
* @param event
|
||||
* @param event the event to convert to {@link StructuredMessageReader}
|
||||
* @param contentType content type to use to resolve the {@link EventFormat}
|
||||
* @return null if format was not found, otherwise returns the built message
|
||||
*/
|
||||
|
@ -60,10 +64,10 @@ public interface StructuredMessageReader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a generic structured message from a {@link CloudEvent}
|
||||
* Create a generic structured message from a {@link CloudEvent}.
|
||||
*
|
||||
* @param event
|
||||
* @param format
|
||||
* @param event the event to convert to {@link StructuredMessageReader}
|
||||
* @param format the format to use to perform the conversion
|
||||
* @return null if format was not found, otherwise returns the built message
|
||||
*/
|
||||
static StructuredMessageReader from(CloudEvent event, EventFormat format) {
|
||||
|
|
|
@ -25,15 +25,15 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
/**
|
||||
* Interface to write the {@link MessageReader} content (CloudEvents attributes, extensions and payload) to a new representation structured representation.
|
||||
*
|
||||
* @param <T> return value at the end of the write process.
|
||||
* @param <R> return value at the end of the write process.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
@FunctionalInterface
|
||||
public interface StructuredMessageWriter<T> {
|
||||
public interface StructuredMessageWriter<R> {
|
||||
|
||||
/**
|
||||
* Write an event using the provided {@link EventFormat}.
|
||||
*/
|
||||
T setEvent(EventFormat format, byte[] value) throws CloudEventRWException;
|
||||
R setEvent(EventFormat format, byte[] value) throws CloudEventRWException;
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ import io.cloudevents.core.message.MessageReader;
|
|||
import io.cloudevents.core.message.StructuredMessageWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Base {@link MessageReader} implementation for a binary message
|
||||
*/
|
||||
public abstract class BaseBinaryMessageReader implements MessageReader {
|
||||
|
||||
@Override
|
||||
|
@ -30,7 +33,7 @@ public abstract class BaseBinaryMessageReader implements MessageReader {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
public <T> T read(StructuredMessageWriter<T> writer) throws CloudEventRWException, IllegalStateException {
|
||||
throw MessageUtils.generateWrongEncoding(Encoding.STRUCTURED, Encoding.BINARY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,18 +56,17 @@ public abstract class BaseGenericBinaryMessageReaderImpl<HK, HV> extends BaseBin
|
|||
// This implementation avoids to use visitAttributes and visitExtensions
|
||||
// in order to complete the visit in one loop
|
||||
this.forEachHeader((key, value) -> {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (isContentTypeHeader(key)) {
|
||||
visitor.withAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
|
||||
visitor.withContextAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
|
||||
} else if (isCloudEventsHeader(key)) {
|
||||
String name = toCloudEventsKey(key);
|
||||
if (name.equals(CloudEventV1.SPECVERSION)) {
|
||||
return;
|
||||
}
|
||||
if (this.version.getAllAttributes().contains(name)) {
|
||||
visitor.withAttribute(name, toCloudEventsValue(value));
|
||||
} else {
|
||||
visitor.withExtension(name, toCloudEventsValue(value));
|
||||
}
|
||||
visitor.withContextAttribute(name, toCloudEventsValue(value));
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -79,14 +78,35 @@ public abstract class BaseGenericBinaryMessageReaderImpl<HK, HV> extends BaseBin
|
|||
return visitor.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key header key
|
||||
* @return true if this header is the content type header, false otherwise
|
||||
*/
|
||||
protected abstract boolean isContentTypeHeader(HK key);
|
||||
|
||||
/**
|
||||
* @param key header key
|
||||
* @return true if this header is a CloudEvents header, false otherwise
|
||||
*/
|
||||
protected abstract boolean isCloudEventsHeader(HK key);
|
||||
|
||||
/**
|
||||
* @param key header key
|
||||
* @return the key converted to a CloudEvents context attribute/extension name
|
||||
*/
|
||||
protected abstract String toCloudEventsKey(HK key);
|
||||
|
||||
/**
|
||||
* Iterate over all the headers in the headers map.
|
||||
*
|
||||
* @param fn header consumer
|
||||
*/
|
||||
protected abstract void forEachHeader(BiConsumer<HK, HV> fn);
|
||||
|
||||
/**
|
||||
* @param value header key
|
||||
* @return the value converted to a valid CloudEvents attribute value as {@link String}.
|
||||
*/
|
||||
protected abstract String toCloudEventsValue(HV value);
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,9 @@ import io.cloudevents.rw.CloudEventDataMapper;
|
|||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import io.cloudevents.rw.CloudEventWriterFactory;
|
||||
|
||||
/**
|
||||
* Base {@link MessageReader} implementation for a structured message
|
||||
*/
|
||||
public abstract class BaseStructuredMessageReader implements MessageReader {
|
||||
|
||||
@Override
|
||||
|
|
|
@ -24,6 +24,9 @@ import io.cloudevents.core.provider.EventFormatProvider;
|
|||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Generic implementation of a structured message.
|
||||
*/
|
||||
public class GenericStructuredMessageReader extends BaseStructuredMessageReader {
|
||||
|
||||
private final EventFormat format;
|
||||
|
@ -35,8 +38,8 @@ public class GenericStructuredMessageReader extends BaseStructuredMessageReader
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
return visitor.setEvent(format, payload);
|
||||
public <T> T read(StructuredMessageWriter<T> writer) throws CloudEventRWException, IllegalStateException {
|
||||
return writer.setEvent(format, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.cloudevents.core.format.EventFormat;
|
|||
import io.cloudevents.core.message.Encoding;
|
||||
import io.cloudevents.core.message.MessageReader;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
@ -31,25 +32,43 @@ import java.util.stream.Stream;
|
|||
|
||||
import static io.cloudevents.rw.CloudEventRWException.newUnknownEncodingException;
|
||||
|
||||
/**
|
||||
* Collection of utilities useful to implement {@link MessageReader} and {@link io.cloudevents.core.message.MessageWriter} related code.
|
||||
*/
|
||||
public class MessageUtils {
|
||||
|
||||
/**
|
||||
* Common flow to parse an incoming message that could be structured or binary.
|
||||
*
|
||||
* @param contentTypeHeaderReader supplier that returns the content type header, if any
|
||||
* @param structuredMessageFactory factory to create the structured {@link MessageReader} from the provided {@link EventFormat}
|
||||
* @param specVersionHeaderReader supplier that returns the spec version header, if any
|
||||
* @param binaryMessageFactory factory to create the binary {@link MessageReader} from the provided {@link SpecVersion}
|
||||
* @return the instantiated {@link MessageReader}
|
||||
* @throws CloudEventRWException if something goes wrong while resolving the {@link SpecVersion} or if the message has unknown encoding
|
||||
*/
|
||||
public static MessageReader parseStructuredOrBinaryMessage(
|
||||
Supplier<String> contentTypeHeaderReader,
|
||||
Function<EventFormat, MessageReader> structuredMessageFactory,
|
||||
Supplier<String> specVersionHeaderReader,
|
||||
Function<SpecVersion, MessageReader> binaryMessageFactory
|
||||
) {
|
||||
) throws CloudEventRWException {
|
||||
// Let's try structured mode
|
||||
String ct = contentTypeHeaderReader.get();
|
||||
if (ct != null) {
|
||||
EventFormat format = EventFormatProvider.getInstance().resolveFormat(ct);
|
||||
if (format != null) {
|
||||
return structuredMessageFactory.apply(format);
|
||||
} else {
|
||||
/**
|
||||
* The format wasn't one we support, but if it's part of the
|
||||
* CloudEvent family it indicates it's a structured
|
||||
* representation that we can't interpret.
|
||||
*/
|
||||
if (ct.startsWith("application/cloudevents")) {
|
||||
throw newUnknownEncodingException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Let's try binary mode
|
||||
|
@ -77,6 +96,11 @@ public class MessageUtils {
|
|||
.collect(Collectors.toMap(Function.identity(), headerNameMapping));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expected the expected encoding
|
||||
* @param actual the actual encoding
|
||||
* @return a new instance of {@link IllegalStateException}.
|
||||
*/
|
||||
public static IllegalStateException generateWrongEncoding(Encoding expected, Encoding actual) {
|
||||
return new IllegalStateException("Cannot visit message as " + expected + " because the actual encoding is " + actual);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package io.cloudevents.core.provider;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* CloudEventValidatorProvider is a singleton class which loads and access CE Validator service providers on behalf of service clients.
|
||||
*/
|
||||
public class CloudEventValidatorProvider {
|
||||
|
||||
private static final CloudEventValidatorProvider cloudEventValidatorProvider = new CloudEventValidatorProvider();
|
||||
|
||||
private final Collection<CloudEventValidator> validators;
|
||||
|
||||
private CloudEventValidatorProvider() {
|
||||
final ServiceLoader<CloudEventValidator> loader = ServiceLoader.load(CloudEventValidator.class);
|
||||
this.validators = new ArrayList<>(2);
|
||||
for (CloudEventValidator cloudEventValidator : loader) {
|
||||
validators.add(cloudEventValidator);
|
||||
}
|
||||
}
|
||||
|
||||
public static CloudEventValidatorProvider getInstance() {
|
||||
return cloudEventValidatorProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* iterates through available Cloudevent validators.
|
||||
*
|
||||
* @param cloudEvent event to validate.
|
||||
*/
|
||||
public void validate(CloudEvent cloudEvent) {
|
||||
for (final CloudEventValidator validator : validators) {
|
||||
validator.validate(cloudEvent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,70 +17,96 @@
|
|||
|
||||
package io.cloudevents.core.provider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import io.cloudevents.core.format.ContentType;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.HashMap;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Singleton holding the discovered {@link EventFormat} implementations through {@link ServiceLoader}.
|
||||
* Singleton holding the discovered {@link EventFormat} implementations through
|
||||
* {@link ServiceLoader}.
|
||||
* <p>
|
||||
* You can resolve an event format using {@code EventFormatProvider.getInstance().resolveFormat(contentType)}.
|
||||
* You can resolve an event format using
|
||||
* {@code EventFormatProvider.getInstance().resolveFormat(contentType)}.
|
||||
* <p>
|
||||
* You can programmatically add a new {@link EventFormat} implementation using {@link #registerFormat(EventFormat)}.
|
||||
* You can programmatically add a new {@link EventFormat} implementation using
|
||||
* {@link #registerFormat(EventFormat)}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class EventFormatProvider {
|
||||
|
||||
private static class SingletonContainer {
|
||||
private final static EventFormatProvider INSTANCE = new EventFormatProvider();
|
||||
}
|
||||
private static class SingletonContainer {
|
||||
private final static EventFormatProvider INSTANCE = new EventFormatProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return instance of {@link EventFormatProvider}
|
||||
*/
|
||||
public static EventFormatProvider getInstance() {
|
||||
return EventFormatProvider.SingletonContainer.INSTANCE;
|
||||
}
|
||||
/**
|
||||
* @return instance of {@link EventFormatProvider}
|
||||
*/
|
||||
public static EventFormatProvider getInstance() {
|
||||
return EventFormatProvider.SingletonContainer.INSTANCE;
|
||||
}
|
||||
|
||||
private final HashMap<String, EventFormat> formats;
|
||||
private final HashMap<String, EventFormat> formats;
|
||||
|
||||
private EventFormatProvider() {
|
||||
this.formats = new HashMap<>();
|
||||
private EventFormatProvider() {
|
||||
this.formats = new HashMap<>();
|
||||
|
||||
StreamSupport.stream(
|
||||
ServiceLoader.load(EventFormat.class).spliterator(),
|
||||
false
|
||||
).forEach(this::registerFormat);
|
||||
}
|
||||
StreamSupport.stream(ServiceLoader.load(EventFormat.class).spliterator(), false)
|
||||
.forEach(this::registerFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new {@link EventFormat} programmatically.
|
||||
*
|
||||
* @param format the new format to register
|
||||
*/
|
||||
public void registerFormat(EventFormat format) {
|
||||
for (String k : format.deserializableContentTypes()) {
|
||||
this.formats.put(k, format);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register a new {@link EventFormat} programmatically.
|
||||
*
|
||||
* @param format the new format to register
|
||||
*/
|
||||
public void registerFormat(EventFormat format) {
|
||||
for (String k : format.deserializableContentTypes()) {
|
||||
this.formats.put(k, format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(String contentType) {
|
||||
int i = contentType.indexOf(';');
|
||||
if (i != -1) {
|
||||
contentType = contentType.substring(0, i);
|
||||
}
|
||||
return this.formats.get(contentType);
|
||||
}
|
||||
/**
|
||||
* Enumerate the supported content types.
|
||||
*
|
||||
* @return an alphabetically sorted list of content types
|
||||
*/
|
||||
public Set<String> getContentTypes() {
|
||||
Set<String> types = new TreeSet<>();
|
||||
types.addAll(this.formats.keySet());
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(String contentType) {
|
||||
int i = contentType.indexOf(';');
|
||||
if (i != -1) {
|
||||
contentType = contentType.substring(0, i);
|
||||
}
|
||||
return this.formats.get(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(ContentType contentType) {
|
||||
return this.formats.get(contentType.value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package io.cloudevents.core.provider;
|
||||
|
||||
import io.cloudevents.CloudEventExtensions;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.extensions.DatarefExtension;
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
@ -30,7 +30,7 @@ import java.util.function.Supplier;
|
|||
/**
|
||||
* Singleton to materialize CloudEvent extensions as POJOs.
|
||||
* <p>
|
||||
* You can materialize an {@link Extension} POJO with {@code ExtensionProvider.getInstance().parseExtension(DistributedTracingExtension.class, event)}.
|
||||
* You can materialize an {@link CloudEventExtension} POJO with {@code ExtensionProvider.getInstance().parseExtension(DistributedTracingExtension.class, event)}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class ExtensionProvider {
|
||||
|
@ -39,6 +39,9 @@ public final class ExtensionProvider {
|
|||
private static final ExtensionProvider INSTANCE = new ExtensionProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return instance of {@link ExtensionProvider}
|
||||
*/
|
||||
public static ExtensionProvider getInstance() {
|
||||
return SingletonContainer.INSTANCE;
|
||||
}
|
||||
|
@ -55,27 +58,28 @@ public final class ExtensionProvider {
|
|||
/**
|
||||
* Register a new extension type.
|
||||
*
|
||||
* @param extensionClass the class implementing {@link Extension}
|
||||
* @param factory the empty arguments factory
|
||||
* @param <T> the type of the extension
|
||||
* @param <T> the type of the extension
|
||||
* @param extensionClass the class implementing {@link CloudEventExtension}
|
||||
* @param factory the empty arguments factory
|
||||
*/
|
||||
public <T extends Extension> void registerExtension(Class<T> extensionClass, Supplier<T> factory) {
|
||||
public <T extends CloudEventExtension> void registerExtension(Class<T> extensionClass, Supplier<T> factory) {
|
||||
this.extensionFactories.put(extensionClass, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an extension from the {@link CloudEventExtensions}, materializing the corresponding POJO.
|
||||
*
|
||||
* @param extensionClass the class implementing {@link Extension}
|
||||
* @param eventExtensions the event extensions to read
|
||||
* @param <T> the type of the extension
|
||||
* @param extensionClass the class implementing {@link CloudEventExtension}
|
||||
* @param eventExtensions the event extensions to read
|
||||
* @return the parsed extension
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <T extends Extension> T parseExtension(Class<T> extensionClass, CloudEventExtensions eventExtensions) {
|
||||
public <T extends CloudEventExtension> T parseExtension(Class<T> extensionClass, CloudEventExtensions eventExtensions) {
|
||||
Supplier<?> factory = extensionFactories.get(extensionClass);
|
||||
if (factory != null) {
|
||||
Extension ext = (Extension) factory.get();
|
||||
CloudEventExtension ext = (CloudEventExtension) factory.get();
|
||||
ext.readFrom(eventExtensions);
|
||||
return (T) ext;
|
||||
}
|
||||
|
|
|
@ -16,16 +16,22 @@
|
|||
*/
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.core.provider.CloudEventValidatorProvider;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
import static io.cloudevents.core.v03.CloudEventV03.*;
|
||||
|
||||
/**
|
||||
* CloudEvent V0.3 builder.
|
||||
|
@ -59,11 +65,10 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
protected void setAttributes(io.cloudevents.CloudEventContext event) {
|
||||
CloudEventContextReader contextReader = CloudEventUtils.toContextReader(event);
|
||||
if (event.getSpecVersion() == SpecVersion.V03) {
|
||||
contextReader.readAttributes(this);
|
||||
contextReader.readContext(this);
|
||||
} else {
|
||||
contextReader.readAttributes(new V1ToV03AttributesConverter(this));
|
||||
contextReader.readContext(new V1ToV03AttributesConverter(this));
|
||||
}
|
||||
contextReader.readExtensions(this);
|
||||
}
|
||||
|
||||
public CloudEventBuilder withId(String id) {
|
||||
|
@ -119,8 +124,15 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
if (type == null) {
|
||||
throw createMissingAttributeException("type");
|
||||
}
|
||||
if (subject != null && subject.isEmpty()) {
|
||||
throw createEmptyAttributeException(("subject"));
|
||||
}
|
||||
|
||||
return new CloudEventV03(id, source, type, time, schemaurl, datacontenttype, subject, this.data, this.extensions);
|
||||
CloudEventV03 cloudEvent = new CloudEventV03(id, source, type, time, schemaurl, datacontenttype, subject, this.data, this.extensions);
|
||||
final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance();
|
||||
validator.validate(cloudEvent);
|
||||
|
||||
return cloudEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -139,65 +151,166 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
}
|
||||
|
||||
// Message impl
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
withId(value);
|
||||
return this;
|
||||
case "source":
|
||||
case SOURCE:
|
||||
try {
|
||||
withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return this;
|
||||
case "type":
|
||||
case TYPE:
|
||||
withType(value);
|
||||
return this;
|
||||
case "datacontenttype":
|
||||
case DATACONTENTTYPE:
|
||||
withDataContentType(value);
|
||||
return this;
|
||||
case "datacontentencoding":
|
||||
case DATACONTENTENCODING:
|
||||
// No-op, this information is not saved in the event because it's useful only for parsing
|
||||
return this;
|
||||
case "schemaurl":
|
||||
case SCHEMAURL:
|
||||
try {
|
||||
withSchemaUrl(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("schemaurl", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SCHEMAURL, value, e);
|
||||
}
|
||||
return this;
|
||||
case "subject":
|
||||
case SUBJECT:
|
||||
withSubject(value);
|
||||
return this;
|
||||
case "time":
|
||||
withTime(Time.parseTime("time", value));
|
||||
case TIME:
|
||||
withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
withSource(value);
|
||||
return this;
|
||||
case "schemaurl":
|
||||
case SCHEMAURL:
|
||||
withDataSchema(value);
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
withTime(value);
|
||||
return this;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
withTime(value);
|
||||
return this;
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
|
||||
{
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, byte[] value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, byte[].class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import io.cloudevents.CloudEventData;
|
|||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.impl.BaseCloudEvent;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -169,43 +169,44 @@ public final class CloudEventV03 extends BaseCloudEvent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException {
|
||||
writer.withAttribute(
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
writer.withContextAttribute(
|
||||
ID,
|
||||
this.id
|
||||
);
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
SOURCE,
|
||||
this.source
|
||||
);
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
TYPE,
|
||||
this.type
|
||||
);
|
||||
if (this.datacontenttype != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
DATACONTENTTYPE,
|
||||
this.datacontenttype
|
||||
);
|
||||
}
|
||||
if (this.schemaurl != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
SCHEMAURL,
|
||||
this.schemaurl
|
||||
);
|
||||
}
|
||||
if (this.subject != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
SUBJECT,
|
||||
this.subject
|
||||
);
|
||||
}
|
||||
if (this.time != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
TIME,
|
||||
this.time
|
||||
);
|
||||
}
|
||||
this.readExtensions(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -235,12 +236,12 @@ public final class CloudEventV03 extends BaseCloudEvent {
|
|||
"id='" + id + '\'' +
|
||||
", source=" + source +
|
||||
", type='" + type + '\'' +
|
||||
", datacontenttype='" + datacontenttype + '\'' +
|
||||
", schemaurl=" + schemaurl +
|
||||
", subject='" + subject + '\'' +
|
||||
", time=" + time +
|
||||
", data=" + getData() +
|
||||
", extensions" + this.extensions +
|
||||
((datacontenttype != null) ? ", datacontenttype='" + datacontenttype + '\'' : "") +
|
||||
((schemaurl != null) ? ", schemaurl=" + schemaurl : "") +
|
||||
((subject != null) ? ", subject='" + subject + '\'' : "") +
|
||||
((time != null) ? ", time=" + time : "") +
|
||||
((getData() != null) ? ", data=" + getData() : "") +
|
||||
", extensions=" + this.extensions +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
|
@ -25,7 +25,9 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
class V1ToV03AttributesConverter implements CloudEventAttributesWriter {
|
||||
import static io.cloudevents.core.v1.CloudEventV1.*;
|
||||
|
||||
class V1ToV03AttributesConverter implements CloudEventContextWriter {
|
||||
|
||||
private final CloudEventBuilder builder;
|
||||
|
||||
|
@ -34,60 +36,114 @@ class V1ToV03AttributesConverter implements CloudEventAttributesWriter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public V1ToV03AttributesConverter withAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
builder.withId(value);
|
||||
return this;
|
||||
case "source":
|
||||
case SOURCE:
|
||||
try {
|
||||
builder.withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return this;
|
||||
case "type":
|
||||
case TYPE:
|
||||
builder.withType(value);
|
||||
return this;
|
||||
case "datacontenttype":
|
||||
case DATACONTENTTYPE:
|
||||
builder.withDataContentType(value);
|
||||
return this;
|
||||
case "dataschema":
|
||||
case DATASCHEMA:
|
||||
try {
|
||||
builder.withSchemaUrl(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(DATASCHEMA, value, e);
|
||||
}
|
||||
return this;
|
||||
case "subject":
|
||||
case SUBJECT:
|
||||
builder.withSubject(value);
|
||||
return this;
|
||||
case "time":
|
||||
builder.withTime(Time.parseTime("time", value));
|
||||
case TIME:
|
||||
builder.withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V1ToV03AttributesConverter withAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
builder.withSource(value);
|
||||
return this;
|
||||
case "dataschema":
|
||||
case DATASCHEMA:
|
||||
builder.withSchemaUrl(value);
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V1ToV03AttributesConverter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
builder.withTime(value);
|
||||
return this;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
builder.withTime(value);
|
||||
return this;
|
||||
case SOURCE:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,10 @@ import io.cloudevents.CloudEvent;
|
|||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.core.provider.CloudEventValidatorProvider;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
|
@ -29,6 +32,8 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import static io.cloudevents.core.v1.CloudEventV1.*;
|
||||
|
||||
/**
|
||||
* CloudEvent V1.0 builder.
|
||||
*
|
||||
|
@ -61,11 +66,10 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
protected void setAttributes(io.cloudevents.CloudEventContext event) {
|
||||
CloudEventContextReader contextReader = CloudEventUtils.toContextReader(event);
|
||||
if (event.getSpecVersion() == SpecVersion.V1) {
|
||||
contextReader.readAttributes(this);
|
||||
contextReader.readContext(this);
|
||||
} else {
|
||||
contextReader.readAttributes(new V03ToV1AttributesConverter(this));
|
||||
contextReader.readContext(new V03ToV1AttributesConverter(this));
|
||||
}
|
||||
contextReader.readExtensions(this);
|
||||
}
|
||||
|
||||
public CloudEventBuilder withId(String id) {
|
||||
|
@ -108,16 +112,24 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
@Override
|
||||
public CloudEvent build() {
|
||||
if (id == null) {
|
||||
throw createMissingAttributeException(CloudEventV1.ID);
|
||||
throw createMissingAttributeException(ID);
|
||||
}
|
||||
if (source == null) {
|
||||
throw createMissingAttributeException(CloudEventV1.SOURCE);
|
||||
throw createMissingAttributeException(SOURCE);
|
||||
}
|
||||
if (type == null) {
|
||||
throw createMissingAttributeException(CloudEventV1.TYPE);
|
||||
throw createMissingAttributeException(TYPE);
|
||||
}
|
||||
if (subject != null && subject.isEmpty()) {
|
||||
throw createEmptyAttributeException(("subject"));
|
||||
}
|
||||
|
||||
return new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions);
|
||||
CloudEvent cloudEvent = new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions);
|
||||
|
||||
final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance();
|
||||
validator.validate(cloudEvent);
|
||||
|
||||
return cloudEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -138,60 +150,157 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
// Message impl
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case CloudEventV1.ID:
|
||||
case ID:
|
||||
withId(value);
|
||||
return this;
|
||||
case CloudEventV1.SOURCE:
|
||||
case SOURCE:
|
||||
try {
|
||||
withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue(CloudEventV1.SOURCE, value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return this;
|
||||
case CloudEventV1.TYPE:
|
||||
case TYPE:
|
||||
withType(value);
|
||||
return this;
|
||||
case CloudEventV1.DATACONTENTTYPE:
|
||||
case DATACONTENTTYPE:
|
||||
withDataContentType(value);
|
||||
return this;
|
||||
case CloudEventV1.DATASCHEMA:
|
||||
case DATASCHEMA:
|
||||
try {
|
||||
withDataSchema(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue(CloudEventV1.DATASCHEMA, value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(DATASCHEMA, value, e);
|
||||
}
|
||||
return this;
|
||||
case CloudEventV1.SUBJECT:
|
||||
case SUBJECT:
|
||||
withSubject(value);
|
||||
return this;
|
||||
case CloudEventV1.TIME:
|
||||
withTime(Time.parseTime(CloudEventV1.TIME, value));
|
||||
case TIME:
|
||||
withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case CloudEventV1.SOURCE:
|
||||
case SOURCE:
|
||||
withSource(value);
|
||||
return this;
|
||||
case CloudEventV1.DATASCHEMA:
|
||||
case DATASCHEMA:
|
||||
withDataSchema(value);
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
if (CloudEventV1.TIME.equals(name)) {
|
||||
withTime(value);
|
||||
return this;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
withTime(value);
|
||||
return this;
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
|
||||
{
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, byte[] value)
|
||||
throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, byte[].class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ package io.cloudevents.core.v1;
|
|||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.impl.BaseCloudEvent;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.net.URI;
|
||||
|
@ -156,43 +156,44 @@ public final class CloudEventV1 extends BaseCloudEvent {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException {
|
||||
writer.withAttribute(
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
writer.withContextAttribute(
|
||||
ID,
|
||||
this.id
|
||||
);
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
SOURCE,
|
||||
this.source
|
||||
);
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
TYPE,
|
||||
this.type
|
||||
);
|
||||
if (this.datacontenttype != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
DATACONTENTTYPE,
|
||||
this.datacontenttype
|
||||
);
|
||||
}
|
||||
if (this.dataschema != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
DATASCHEMA,
|
||||
this.dataschema
|
||||
);
|
||||
}
|
||||
if (this.subject != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
SUBJECT,
|
||||
this.subject
|
||||
);
|
||||
}
|
||||
if (this.time != null) {
|
||||
writer.withAttribute(
|
||||
writer.withContextAttribute(
|
||||
TIME,
|
||||
this.time
|
||||
);
|
||||
}
|
||||
this.readExtensions(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -222,11 +223,11 @@ public final class CloudEventV1 extends BaseCloudEvent {
|
|||
"id='" + id + '\'' +
|
||||
", source=" + source +
|
||||
", type='" + type + '\'' +
|
||||
", datacontenttype='" + datacontenttype + '\'' +
|
||||
", dataschema=" + dataschema +
|
||||
", subject='" + subject + '\'' +
|
||||
", time=" + time +
|
||||
", data=" + getData() +
|
||||
((datacontenttype != null) ? ", datacontenttype='" + datacontenttype + '\'' : "") +
|
||||
((dataschema != null) ? ", dataschema=" + dataschema : "") +
|
||||
((subject != null) ? ", subject='" + subject + '\'' : "") +
|
||||
((time != null) ? ", time=" + time : "") +
|
||||
((getData() != null) ? ", data=" + getData() : "") +
|
||||
", extensions=" + this.extensions +
|
||||
'}';
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
|
||||
package io.cloudevents.core.v1;
|
||||
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
|
@ -25,7 +25,9 @@ import java.net.URI;
|
|||
import java.net.URISyntaxException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
class V03ToV1AttributesConverter implements CloudEventAttributesWriter {
|
||||
import static io.cloudevents.core.v03.CloudEventV03.*;
|
||||
|
||||
class V03ToV1AttributesConverter implements CloudEventContextWriter {
|
||||
|
||||
private final CloudEventBuilder builder;
|
||||
|
||||
|
@ -34,60 +36,114 @@ class V03ToV1AttributesConverter implements CloudEventAttributesWriter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public V03ToV1AttributesConverter withAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
builder.withId(value);
|
||||
return this;
|
||||
case "source":
|
||||
case SOURCE:
|
||||
try {
|
||||
builder.withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return this;
|
||||
case "type":
|
||||
case TYPE:
|
||||
builder.withType(value);
|
||||
return this;
|
||||
case "datacontenttype":
|
||||
case DATACONTENTTYPE:
|
||||
builder.withDataContentType(value);
|
||||
return this;
|
||||
case "schemaurl":
|
||||
case SCHEMAURL:
|
||||
try {
|
||||
builder.withDataSchema(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SCHEMAURL, value, e);
|
||||
}
|
||||
return this;
|
||||
case "subject":
|
||||
case SUBJECT:
|
||||
builder.withSubject(value);
|
||||
return this;
|
||||
case "time":
|
||||
builder.withTime(Time.parseTime("time", value));
|
||||
case TIME:
|
||||
builder.withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V03ToV1AttributesConverter withAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
builder.withSource(value);
|
||||
return this;
|
||||
case "schemaurl":
|
||||
case SCHEMAURL:
|
||||
builder.withDataSchema(value);
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V03ToV1AttributesConverter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
builder.withTime(value);
|
||||
return this;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
builder.withTime(value);
|
||||
return this;
|
||||
case SOURCE:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,15 +14,20 @@
|
|||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.http.restful.ws.impl;
|
||||
package io.cloudevents.core.validator;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static boolean isCloudEventEntity(Object obj) {
|
||||
return obj != null && CloudEvent.class.isAssignableFrom(obj.getClass());
|
||||
}
|
||||
/**
|
||||
* @author Vinay Bhat
|
||||
* Interface which defines validation for CloudEvents attributes and extensions.
|
||||
*/
|
||||
public interface CloudEventValidator {
|
||||
|
||||
/**
|
||||
* Validate the attributes of a CloudEvent.
|
||||
*
|
||||
* @param cloudEvent the CloudEvent to validate
|
||||
*/
|
||||
void validate(CloudEvent cloudEvent);
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package io.cloudevents.core.data;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -8,7 +10,7 @@ class PojoCloudEventDataTest {
|
|||
|
||||
@Test
|
||||
void testWrapAndMemoization() {
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, i -> i.toString().getBytes());
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, i -> i.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(data.getValue())
|
||||
.isEqualTo(10);
|
||||
|
@ -16,7 +18,7 @@ class PojoCloudEventDataTest {
|
|||
byte[] firstConversion = data.toBytes();
|
||||
|
||||
assertThat(firstConversion)
|
||||
.isEqualTo("10".getBytes());
|
||||
.isEqualTo("10".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(data.toBytes())
|
||||
.isSameAs(firstConversion);
|
||||
|
@ -24,7 +26,7 @@ class PojoCloudEventDataTest {
|
|||
|
||||
@Test
|
||||
void testAlreadySerializedValue() {
|
||||
byte[] serialized = "10".getBytes();
|
||||
byte[] serialized = "10".getBytes(StandardCharsets.UTF_8);
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, v -> serialized);
|
||||
|
||||
assertThat(data.getValue())
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.core.test.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static io.cloudevents.core.test.Data.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class BaseCloudEventBuilderTest {
|
||||
|
||||
|
@ -49,17 +50,23 @@ public class BaseCloudEventBuilderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLongExtensionName() {
|
||||
Exception exception = assertThrows(RuntimeException.class, () -> {
|
||||
CloudEvent cloudEvent = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
public void testLongExtensionNameV1() {
|
||||
assertDoesNotThrow(() -> {
|
||||
CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("thisextensionnameistoolong", "")
|
||||
.build();
|
||||
});
|
||||
String expectedMessage = "Invalid extensions name: thisextensionnameistoolong";
|
||||
String actualMessage = exception.getMessage();
|
||||
|
||||
assertTrue(actualMessage.contains(expectedMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongExtensionNameV03() {
|
||||
assertDoesNotThrow(() -> {
|
||||
CloudEventBuilder.v03(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("thisextensionnameistoolong", "")
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidExtensionName() {
|
||||
Exception exception = assertThrows(RuntimeException.class, () -> {
|
||||
|
@ -72,4 +79,64 @@ public class BaseCloudEventBuilderTest {
|
|||
|
||||
assertTrue(actualMessage.contains(expectedMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryExtension() {
|
||||
|
||||
final String EXT_NAME = "verifyme";
|
||||
|
||||
CloudEvent given = CloudEventBuilder.v1(Data.V1_MIN)
|
||||
.withExtension(EXT_NAME, Data.BINARY_VALUE)
|
||||
.build();
|
||||
|
||||
// Sanity
|
||||
assertNotNull(given);
|
||||
|
||||
// Did the extension stick
|
||||
assertTrue(given.getExtensionNames().contains(EXT_NAME));
|
||||
assertNotNull(given.getExtension(EXT_NAME));
|
||||
|
||||
// Does the extension have the right value
|
||||
assertEquals(Data.BINARY_VALUE, given.getExtension(EXT_NAME));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutDataRemovesDataAttributeFromCopiedCloudEvent() {
|
||||
CloudEvent original = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
CloudEvent copy = CloudEventBuilder.v1(original).withoutData().build();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(copy.getData()).isNull(),
|
||||
() -> assertThat(copy.getDataContentType()).isEqualTo(DATACONTENTTYPE_JSON),
|
||||
() -> assertThat(copy.getDataSchema()).isEqualTo(DATASCHEMA)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutDataContentTypeRemovesDataContentTypeAttributeFromCopiedCloudEvent() {
|
||||
CloudEvent original = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
CloudEvent copy = CloudEventBuilder.v1(original).withoutDataContentType().build();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(Objects.requireNonNull(copy.getData()).toBytes()).isEqualTo(DATA_JSON_SERIALIZED),
|
||||
() -> assertThat(copy.getDataContentType()).isNull(),
|
||||
() -> assertThat(copy.getDataSchema()).isEqualTo(DATASCHEMA)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutDataSchemaRemovesDataSchemaAttributeFromCopiedCloudEvent() {
|
||||
CloudEvent original = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
CloudEvent copy = CloudEventBuilder.v1(original).withoutDataSchema().build();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(Objects.requireNonNull(copy.getData()).toBytes()).isEqualTo(DATA_JSON_SERIALIZED),
|
||||
() -> assertThat(copy.getDataContentType()).isEqualTo(DATACONTENTTYPE_JSON),
|
||||
() -> assertThat(copy.getDataSchema()).isNull()
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,4 +91,28 @@ public class CloudEventImplTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringV1() {
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.build();
|
||||
|
||||
assertThat(event.toString())
|
||||
.doesNotContain("time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringV03() {
|
||||
CloudEvent event = CloudEventBuilder.v03()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.build();
|
||||
|
||||
assertThat(event.toString())
|
||||
.doesNotContain("time");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* 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
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.impl;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class StringUtilsTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("startsWithIgnoreCaseArgs")
|
||||
public void startsWithIgnoreCase(final String s, final String prefix, final boolean expected) {
|
||||
Assertions.assertThat(StringUtils.startsWithIgnoreCase(s, prefix)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> startsWithIgnoreCaseArgs() {
|
||||
return Stream.of(
|
||||
Arguments.of("s", "s", true),
|
||||
Arguments.of("sa", "S", true),
|
||||
Arguments.of("saS", "As", false),
|
||||
Arguments.of("sasso", "SASO", false),
|
||||
Arguments.of("sasso", "SaSsO", true)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -27,6 +27,25 @@ class MessageUtilsTest {
|
|||
.isEqualTo(CloudEventRWException.CloudEventRWExceptionKind.UNKNOWN_ENCODING);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify an exception is thrown if an unsupported
|
||||
* application/cloudevents content-type family is
|
||||
* received.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testBadContentTypes(String contentType) {
|
||||
|
||||
CloudEventRWException exception = assertThrows(CloudEventRWException.class, () ->
|
||||
{
|
||||
parseStructuredOrBinaryMessage(() -> contentType, eventFormat -> null, () -> "1.0", specVersion -> null);
|
||||
});
|
||||
|
||||
assertThat(exception.getKind()).isEqualTo(CloudEventRWException.CloudEventRWExceptionKind.UNKNOWN_ENCODING);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseStructuredOrBinaryMessage_StructuredMode() {
|
||||
MessageUtils.parseStructuredOrBinaryMessage(() -> "application/cloudevents+csv;",
|
||||
|
@ -54,4 +73,11 @@ class MessageUtilsTest {
|
|||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testBadContentTypes() {
|
||||
return Stream.of(
|
||||
Arguments.of("application/cloudevents"),
|
||||
Arguments.of("application/cloudevents+morse")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class CSVFormat implements EventFormat {
|
|||
event.getData() != null
|
||||
? new String(Base64.getEncoder().encode(event.getData().toBytes()), StandardCharsets.UTF_8)
|
||||
: "null"
|
||||
).getBytes();
|
||||
).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,7 +70,7 @@ public class CSVFormat implements EventFormat {
|
|||
URI dataschema = splitted[5].equals("null") ? null : URI.create(splitted[5]);
|
||||
String subject = splitted[6].equals("null") ? null : splitted[6];
|
||||
OffsetDateTime time = splitted[7].equals("null") ? null : Time.parseTime(splitted[7]);
|
||||
byte[] data = splitted[8].equals("null") ? null : Base64.getDecoder().decode(splitted[8].getBytes());
|
||||
byte[] data = splitted[8].equals("null") ? null : Base64.getDecoder().decode(splitted[8].getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
CloudEventBuilder builder = CloudEventBuilder.fromSpecVersion(sv)
|
||||
.withId(id)
|
||||
|
|
|
@ -34,24 +34,21 @@ import java.util.Map;
|
|||
public class MockBinaryMessageWriter extends BaseBinaryMessageReader implements MessageReader, CloudEventContextReader, CloudEventWriterFactory<MockBinaryMessageWriter, MockBinaryMessageWriter>, CloudEventWriter<MockBinaryMessageWriter> {
|
||||
|
||||
private SpecVersion version;
|
||||
private Map<String, Object> attributes;
|
||||
private Map<String, Object> context;
|
||||
private CloudEventData data;
|
||||
private Map<String, Object> extensions;
|
||||
|
||||
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> attributes, CloudEventData data, Map<String, Object> extensions) {
|
||||
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> context, CloudEventData data) {
|
||||
this.version = version;
|
||||
this.attributes = attributes;
|
||||
this.context = context;
|
||||
this.data = data;
|
||||
this.extensions = extensions;
|
||||
}
|
||||
|
||||
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> attributes, byte[] data, Map<String, Object> extensions) {
|
||||
this(version, attributes, BytesCloudEventData.wrap(data), extensions);
|
||||
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> context, byte[] data) {
|
||||
this(version, context, BytesCloudEventData.wrap(data));
|
||||
}
|
||||
|
||||
public MockBinaryMessageWriter() {
|
||||
this.attributes = new HashMap<>();
|
||||
this.extensions = new HashMap<>();
|
||||
this.context = new HashMap<>();
|
||||
}
|
||||
|
||||
public MockBinaryMessageWriter(CloudEvent event) {
|
||||
|
@ -67,47 +64,14 @@ public class MockBinaryMessageWriter extends BaseBinaryMessageReader implements
|
|||
throw new IllegalStateException("MockBinaryMessage is empty");
|
||||
}
|
||||
|
||||
CloudEventWriter<V> visitor = writerFactory.create(version);
|
||||
this.readAttributes(visitor);
|
||||
this.readExtensions(visitor);
|
||||
CloudEventWriter<V> writer = writerFactory.create(version);
|
||||
this.readContext(writer);
|
||||
|
||||
if (this.data != null) {
|
||||
return visitor.end(mapper.map(this.data));
|
||||
return writer.end(mapper.map(this.data));
|
||||
}
|
||||
|
||||
return visitor.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException, IllegalStateException {
|
||||
for (Map.Entry<String, Object> e : this.attributes.entrySet()) {
|
||||
if (e.getValue() instanceof String) {
|
||||
writer.withAttribute(e.getKey(), (String) e.getValue());
|
||||
} else if (e.getValue() instanceof OffsetDateTime) {
|
||||
writer.withAttribute(e.getKey(), (OffsetDateTime) e.getValue());
|
||||
} else if (e.getValue() instanceof URI) {
|
||||
writer.withAttribute(e.getKey(), (URI) e.getValue());
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside attributes map: " + e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtensions(CloudEventExtensionsWriter writer) throws CloudEventRWException, IllegalStateException {
|
||||
for (Map.Entry<String, Object> entry : this.extensions.entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
writer.withExtension(entry.getKey(), (String) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Number) {
|
||||
writer.withExtension(entry.getKey(), (Number) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Boolean) {
|
||||
writer.withExtension(entry.getKey(), (Boolean) entry.getValue());
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
|
||||
}
|
||||
}
|
||||
return writer.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -121,46 +85,60 @@ public class MockBinaryMessageWriter extends BaseBinaryMessageReader implements
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter withAttribute(String name, String value) throws CloudEventRWException {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter withAttribute(String name, URI value) throws CloudEventRWException {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
this.attributes.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter withExtension(String name, String value) throws CloudEventRWException {
|
||||
this.extensions.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter withExtension(String name, Number value) throws CloudEventRWException {
|
||||
this.extensions.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter withExtension(String name, Boolean value) throws CloudEventRWException {
|
||||
this.extensions.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MockBinaryMessageWriter create(SpecVersion version) {
|
||||
this.version = version;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
for (Map.Entry<String, Object> entry : this.context.entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
writer.withContextAttribute(entry.getKey(), (String) entry.getValue());
|
||||
} else if (entry.getValue() instanceof OffsetDateTime) {
|
||||
writer.withContextAttribute(entry.getKey(), (OffsetDateTime) entry.getValue());
|
||||
} else if (entry.getValue() instanceof URI) {
|
||||
writer.withContextAttribute(entry.getKey(), (URI) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Number) {
|
||||
writer.withContextAttribute(entry.getKey(), (Number) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Boolean) {
|
||||
writer.withContextAttribute(entry.getKey(), (Boolean) entry.getValue());
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside context map: " + entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
this.context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
this.context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
this.context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
this.context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
this.context.put(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,12 +41,12 @@ public class MockStructuredMessageReader extends BaseStructuredMessageReader imp
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
public <T> T read(StructuredMessageWriter<T> writer) throws CloudEventRWException, IllegalStateException {
|
||||
if (this.format == null) {
|
||||
throw new IllegalStateException("MockStructuredMessage is empty");
|
||||
}
|
||||
|
||||
return visitor.setEvent(this.format, this.payload);
|
||||
return writer.setEvent(this.format, this.payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.cloudevents.core.mock;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MyCloudEventData implements CloudEventData {
|
||||
|
@ -14,7 +14,7 @@ public class MyCloudEventData implements CloudEventData {
|
|||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return Integer.toString(value).getBytes();
|
||||
return Integer.toString(value).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
|
|
|
@ -36,4 +36,9 @@ public class EventFormatProviderTest {
|
|||
.isInstanceOf(CSVFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listTypes() {
|
||||
assertThat(EventFormatProvider.getInstance().getContentTypes()).hasSize(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package io.cloudevents.core.test;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
|
||||
public class CloudEventCustomValidator implements CloudEventValidator {
|
||||
|
||||
@Override
|
||||
public void validate(CloudEvent cloudEvent) {
|
||||
String namespace = null;
|
||||
if ((namespace = (String) cloudEvent.getExtension("namespace")) != null &&
|
||||
!namespace.equals("sales")){
|
||||
throw new IllegalStateException("Expecting sales in namespace extension");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,9 @@ import io.cloudevents.CloudEvent;
|
|||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -38,9 +40,10 @@ public class Data {
|
|||
public static final String SUBJECT = "sub";
|
||||
public static final OffsetDateTime TIME = Time.parseTime("2018-04-26T14:48:09+02:00");
|
||||
|
||||
public static byte[] DATA_JSON_SERIALIZED = "{}".getBytes();
|
||||
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes();
|
||||
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes();
|
||||
public static byte[] DATA_JSON_SERIALIZED = "{}".getBytes(StandardCharsets.UTF_8);
|
||||
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes(StandardCharsets.UTF_8);
|
||||
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes(StandardCharsets.UTF_8);
|
||||
public static byte[] BINARY_VALUE = { (byte) 0xE0, (byte) 0xFF, (byte) 0x00, (byte) 0x44, (byte) 0xAA }; // Base64: 4P8ARKo=
|
||||
|
||||
public static final CloudEvent V1_MIN = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
|
@ -108,6 +111,23 @@ public class Data {
|
|||
.withTime(TIME)
|
||||
.build();
|
||||
|
||||
public static final CloudEvent V1_WITH_BINARY_EXT = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.withExtension("binary", BINARY_VALUE)
|
||||
.build();
|
||||
|
||||
public static final CloudEvent V1_WITH_NUMERIC_EXT = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.withExtension("integer", 42)
|
||||
.withExtension("decimal", new BigDecimal("42.42"))
|
||||
.withExtension("float", 4.2f)
|
||||
.withExtension("long", new Long(4200))
|
||||
.build();
|
||||
|
||||
public static final CloudEvent V03_MIN = CloudEventBuilder.v03(V1_MIN).build();
|
||||
public static final CloudEvent V03_WITH_JSON_DATA = CloudEventBuilder.v03(V1_WITH_JSON_DATA).build();
|
||||
public static final CloudEvent V03_WITH_JSON_DATA_WITH_EXT = CloudEventBuilder.v03(V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
|
@ -137,6 +157,18 @@ public class Data {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the nature of CE there are scenarios where an event might be serialized
|
||||
* in such a fashion that it can not be deserialized while retaining the orginal
|
||||
* type information, this varies from format-2-format
|
||||
*/
|
||||
|
||||
public static Stream<CloudEvent> v1NonRoundTripEvents() {
|
||||
return Stream.of(
|
||||
Data.V1_WITH_BINARY_EXT
|
||||
);
|
||||
}
|
||||
|
||||
public static Stream<CloudEvent> v03Events() {
|
||||
return Stream.of(
|
||||
Data.V03_MIN,
|
||||
|
|
|
@ -158,4 +158,27 @@ public class CloudEventBuilderTest {
|
|||
).hasMessageContaining("Attribute 'type' cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingSubject() {
|
||||
CloudEvent actual = CloudEventBuilder
|
||||
.v03()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.build();
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptySubject() {
|
||||
assertThatCode(() -> CloudEventBuilder
|
||||
.v03()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.withSubject("")
|
||||
.build()
|
||||
).hasMessageContaining("Attribute 'subject' cannot be empty");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,4 +142,39 @@ public class CloudEventBuilderTest {
|
|||
).hasMessageContaining("Attribute 'type' cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorProvider(){
|
||||
assertThatCode(() -> CloudEventBuilder
|
||||
.v1()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.withExtension("namespace", "order")
|
||||
.build()
|
||||
).hasMessageContaining("Expecting sales in namespace extension");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingSubject() {
|
||||
CloudEvent actual = CloudEventBuilder
|
||||
.v1()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.build();
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptySubject() {
|
||||
assertThatCode(() -> CloudEventBuilder
|
||||
.v1()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.withSubject("")
|
||||
.build()
|
||||
).hasMessageContaining("Attribute 'subject' cannot be empty");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.cloudevents.core.test.CloudEventCustomValidator
|
|
@ -1,14 +1,14 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.3.4)
|
||||
activesupport (6.0.6.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
|
@ -16,7 +16,7 @@ GEM
|
|||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.7)
|
||||
concurrent-ruby (1.2.0)
|
||||
dnsruby (1.61.5)
|
||||
simpleidn (~> 0.1)
|
||||
em-websocket (0.5.2)
|
||||
|
@ -82,7 +82,7 @@ GEM
|
|||
octokit (~> 4.0)
|
||||
public_suffix (~> 3.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.14.0)
|
||||
html-pipeline (2.14.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
|
@ -203,30 +203,32 @@ GEM
|
|||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.3.0)
|
||||
listen (3.7.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.4.0)
|
||||
mini_portile2 (2.8.8)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.14.2)
|
||||
minitest (5.17.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.10.10)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
nokogiri (1.18.3)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
octokit (4.19.0)
|
||||
faraday (>= 0.9)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (3.1.1)
|
||||
racc (1.8.1)
|
||||
rake (13.0.1)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.4)
|
||||
rexml (3.3.9)
|
||||
rouge (3.23.0)
|
||||
ruby-enum (0.8.0)
|
||||
i18n
|
||||
|
@ -248,13 +250,13 @@ GEM
|
|||
thread_safe (0.3.6)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.8)
|
||||
tzinfo (1.2.11)
|
||||
thread_safe (~> 0.1)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
zeitwerk (2.4.1)
|
||||
zeitwerk (2.6.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
|
@ -4,19 +4,19 @@
|
|||
title: Java SDK for CloudEvents
|
||||
remote_theme: pmarsceill/just-the-docs
|
||||
plugins:
|
||||
- jemoji
|
||||
- jemoji
|
||||
|
||||
search_enabled: true
|
||||
|
||||
gh_edit_link: true
|
||||
gh_edit_link_text: "Edit this page on GitHub."
|
||||
gh_edit_repository: "https://github.com/cloudevents/sdk-java"
|
||||
gh_edit_branch: "master"
|
||||
gh_edit_branch: "main"
|
||||
gh_edit_source: docs
|
||||
gh_edit_view_mode: "tree"
|
||||
|
||||
aux_links:
|
||||
"GitHub Repository":
|
||||
- "https://github.com/cloudevents/sdk-java"
|
||||
"CloudEvents home":
|
||||
- "https://cloudevents.io/"
|
||||
"GitHub Repository":
|
||||
- "https://github.com/cloudevents/sdk-java"
|
||||
"CloudEvents home":
|
||||
- "https://cloudevents.io/"
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: CloudEvents AMQP Proton
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# CloudEvents AMQP Proton
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp-proton)
|
||||
|
||||
This module implements `MessageReader` and `MessageWriter` using the Qpid Proton
|
||||
library. It can be used with Qpid Proton or any integrations based on Qpid
|
||||
Proton (e.g vertx-proton).
|
||||
|
||||
For Maven based projects, use the following to configure the `proton` AMQP
|
||||
binding for CloudEvents:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Sending and Receiving CloudEvents
|
||||
|
||||
To send and receive CloudEvents we use `MessageWriter` and `MessageReader`,
|
||||
respectively. This module offers factory methods for creation of those in
|
||||
`ProtonAmqpMessageFactory`.
|
||||
|
||||
```java
|
||||
public class ProtonAmqpMessageFactory {
|
||||
public static MessageReader createReader(final Message message);
|
||||
|
||||
public static MessageReader createReader(final String contentType, final ApplicationProperties props, @Nullable final Section body);
|
||||
|
||||
public static MessageWriter createWriter();
|
||||
}
|
||||
```
|
||||
|
||||
## Examples:
|
||||
|
||||
The example uses the `vertx-proton` integration to send/receive CloudEvent
|
||||
messages over AMQP:
|
||||
|
||||
- [Vertx AmqpServer](https://github.com/cloudevents/sdk-java/tree/main/examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpServer.java)
|
||||
- [Vertx AmqpClient](https://github.com/cloudevents/sdk-java/tree/main/examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpClient.java)
|
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
title: CloudEvents API
|
||||
nav_order: 2
|
||||
---
|
||||
|
||||
# CloudEvents API
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
|
||||
This module contains the interfaces to represent `CloudEvent` in memory and to
|
||||
read and write an object using as CloudEvent.
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## `CloudEvent` hierarchy
|
||||
|
||||
`CloudEvent` is the main interface representing a read-only CloudEvent in-memory
|
||||
representation. A `CloudEvent` is composed by its context attributes, including
|
||||
the predefined attributes and the extensions, and the data.
|
||||
|
||||

|
||||
|
||||
`CloudEventData` is an abstraction to allow carrying any kind of data payload
|
||||
inside a `CloudEvent`, while enforcing the ability of convert such data to
|
||||
`[]byte`, ultimately used to send `CloudEvent` on the wire.
|
||||
|
||||
## Reader and Writer
|
||||
|
||||
The package `io.cloudevents.rw` contains the interfaces to read and write
|
||||
objects as CloudEvents.
|
||||
|
||||

|
||||
|
||||
In other words, you can use these interfaces to perform an unstructured
|
||||
read/write of an entity as CloudEvent. For example, an HTTP server request in
|
||||
binary mode containing a valid CloudEvent can be translated to a
|
||||
`CloudEventReader`. Similarly, an HTTP server response can be written as a
|
||||
CloudEvent, hence an eventual response builder could implement
|
||||
`CloudEventWriter`.
|
||||
|
||||
`CloudEventReader` and `CloudEventWriter` implementations doesn't have any
|
||||
particular knowledge about specification version, difference between attributes
|
||||
and extensions, and so on. Their only concern is how to read and write context
|
||||
attributes and data back and forth to the "CloudEvents type system", as defined
|
||||
in the package `io.cloudevents`.
|
||||
|
||||
A 3rd party implementer can implement these interfaces directly in its
|
||||
`CloudEvent` in order to customize/implement efficiently the
|
||||
marshalling/unmarshalling process. These interfaces are optional and, if your
|
||||
`CloudEvent` doesn't implement it, a default implementation is provided by the
|
||||
core module.
|
||||
|
||||
## Other interfaces
|
||||
|
||||
- `CloudEventExtension` represents a _materialized_ in-memory representation of a CloudEvent extension
|
||||
- `SpecVersion` is an enum of CloudEvents' specification versions supported by this SDK version.
|
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
|
@ -0,0 +1,49 @@
|
|||
---
|
||||
title: CloudEvents Avro Compact
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||
# CloudEvents Avro Compact
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact)
|
||||
|
||||
This module provides the Avro Compact `EventFormat` implementation.
|
||||
|
||||
# Setup
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-avro-compact</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
No further configuration is required is use the module.
|
||||
|
||||
## Using the Avro Compact Event Format
|
||||
|
||||
### Event serialization
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.avro.avro.compact.AvroCompactFormat;
|
||||
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
|
||||
byte[] serialized = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(AvroCompactFormat.CONTENT_TYPE)
|
||||
.serialize(event);
|
||||
```
|
||||
|
||||
The `EventFormatProvider` will automatically resolve the format using the
|
||||
`ServiceLoader` APIs.
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
---
|
||||
title: CloudEvents Core
|
||||
nav_order: 3
|
||||
---
|
||||
|
||||
# CloudEvents Core
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
|
||||
This package includes implementations and utilities to create and process
|
||||
`CloudEvent` and interfaces to deal with Protocol Bindings and Event Formats.
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Using `CloudEvent`s
|
||||
|
||||
To create an event, you can use the `CloudEventBuilder`:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
final CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("000")
|
||||
.withType("example.demo")
|
||||
.withSource(URI.create("http://example.com"))
|
||||
.withData("text/plain","Hello world!".getBytes("UTF-8"))
|
||||
.build();
|
||||
```
|
||||
|
||||
Now you can access the event context attributes and data:
|
||||
|
||||
```java
|
||||
// Get an event attribute
|
||||
String id = event.getId();
|
||||
|
||||
// Get an event extension
|
||||
Object someExtension = event.getExtension("myextension");
|
||||
|
||||
// Retrieve the event data
|
||||
CloudEventData data = event.getData();
|
||||
```
|
||||
|
||||
### Creating and accessing to the event data
|
||||
|
||||
`CloudEventData` is an abstraction that can wrap any kind of data payload, while
|
||||
still enforcing the conversion to bytes. To convert to bytes:
|
||||
|
||||
```java
|
||||
// Convert the event data to bytes
|
||||
byte[]bytes = cloudEventData.toBytes();
|
||||
```
|
||||
|
||||
If you want to map the JSON event data to POJOs using Jackson, check out the
|
||||
[CloudEvents Jackson documentation](json-jackson.md).
|
||||
|
||||
When building an event, you can wrap a POJO inside its payload using
|
||||
`PojoCloudEventData`, providing the function to convert it back to bytes:
|
||||
|
||||
```java
|
||||
CloudEventData pojoData = PojoCloudEventData.wrap(myPojo, myPojo::toBytes);
|
||||
```
|
||||
|
||||
### Using Event Formats
|
||||
|
||||
The SDK implements
|
||||
[Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format)
|
||||
in various submodules, but keeping a single entrypoint for users who wants to
|
||||
serialize/deserialize events back and forth to event formats.
|
||||
|
||||
To use an Event Format, you just need to add the event format implementation you
|
||||
prefer as dependency in your project and the core module, through the
|
||||
`ServiceLoader` mechanism, will load it into the classpath. For example, to use
|
||||
the
|
||||
[JSON event format](https://github.com/cloudevents/spec/blob/v1.0/json-format.md)
|
||||
with Jackson, add `cloudevents-json-jackson` as a dependency and then using the
|
||||
`EventFormatProvider`:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
|
||||
EventFormat format = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(JsonFormat.CONTENT_TYPE);
|
||||
|
||||
// Serialize event
|
||||
byte[] serialized = format.serialize(event);
|
||||
|
||||
// Deserialize event
|
||||
CloudEvent event = format.deserialize(bytes);
|
||||
```
|
||||
|
||||
### Materialize an Extension
|
||||
|
||||
CloudEvent extensions can be materialized in their respective POJOs using the
|
||||
`ExtensionProvider`:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.core.provider.ExtensionProvider;
|
||||
|
||||
DistributedTracingExtension dte = ExtensionProvider.getInstance()
|
||||
.parseExtension(DistributedTracingExtension.class, event);
|
||||
```
|
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
title: CloudEvents HTTP Basic
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# Generic HTTP Protocol Binding
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-http-basic)
|
||||
|
||||
This module is designed to be usable with various HTTP APIs.
|
||||
|
||||
There are also more specialized HTTP bindings:
|
||||
|
||||
- [`cloudevents-http-vertx`](http-vertx.md)
|
||||
- [`cloudevents-http-restful-ws`](http-jakarta-restful-ws.md)
|
||||
- [`cloudevents-spring`](spring.md)
|
||||
|
||||
Since this module is generic it doesn't offer optimal performance for all HTTP
|
||||
implementations. For better performance consider implementing `MessageReader`
|
||||
and `MessageWriter` that are tailored for specific HTTP implementation. As a
|
||||
reference you can take aforementioned existing bindings.
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Generic
|
||||
HTTP Transport:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-basic</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Sending and Receiving CloudEvents
|
||||
|
||||
To send and receive CloudEvents we use `MessageWriter` and `MessageReader`,
|
||||
respectively. This module offers factory methods for creation of those in
|
||||
`HttpMessageFactory`.
|
||||
|
||||
```java
|
||||
public class HttpMessageFactory {
|
||||
public static MessageReader createReader(Consumer<BiConsumer<String,String>> forEachHeader, byte[] body);
|
||||
public static MessageReader createReader(Map<String,String> headers, byte[] body);
|
||||
public static MessageReader createReaderFromMultimap(Map<String,List<String>> headers, byte[] body);
|
||||
public static MessageWriter createWriter(BiConsumer<String, String> putHeader, Consumer<byte[]> sendBody);
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- [Standard Java HttpServer](https://github.com/cloudevents/sdk-java/tree/main/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/BasicHttpServer.java)
|
||||
- [Http Client with HttpURLConnection](https://github.com/cloudevents/sdk-java/tree/main/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/HttpURLConnectionClient.java)
|
||||
- [Http Servlet with Jetty](https://github.com/cloudevents/sdk-java/tree/main/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/JettyServer.java)
|
|
@ -0,0 +1,137 @@
|
|||
---
|
||||
title: CloudEvents HTTP Jakarta EE 9+ - Jakarta RESTful Web Services
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# HTTP Protocol Binding for Jakarta EE 9+ - Jakarta RESTful Web Services
|
||||
|
||||
[](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Jakarta
|
||||
RESTful Web Services Binding for Jakarta EE 9+:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws-jakarta</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
This integration is tested with Jersey (Requires JDK11 or higher), RestEasy & Microprofile Liberty.
|
||||
|
||||
#### * Before using this package ensure your web framework does support the `jakarta.*` namespace.
|
||||
|
||||
## Receiving CloudEvents
|
||||
|
||||
You need to configure the `CloudEventsProvider` to enable
|
||||
marshalling/unmarshalling of CloudEvents.
|
||||
|
||||
Below is a sample on how to read and write CloudEvents:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
@Path("/")
|
||||
public class EventReceiverResource {
|
||||
|
||||
|
||||
|
||||
@GET
|
||||
@Path("getMinEvent")
|
||||
public CloudEvent getMinEvent() {
|
||||
return CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Return the CloudEvent using the HTTP binding structured encoding
|
||||
@GET
|
||||
@Path("getStructuredEvent")
|
||||
@StructuredEncoding("application/cloudevents+csv")
|
||||
public CloudEvent getStructuredEvent() {
|
||||
return CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("postEventWithoutBody")
|
||||
public Response postEvent(CloudEvent inputEvent) {
|
||||
// Handle the event
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending CloudEvents
|
||||
|
||||
You need to configure the `CloudEventsProvider` to enable
|
||||
marshalling/unmarshalling of CloudEvents.
|
||||
|
||||
Below is a sample on how to use the client to send a CloudEvent:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.http.restful.ws.CloudEventsProvider;
|
||||
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
public class CloudEventSender {
|
||||
|
||||
public Response sendEvent(WebTarget target, CloudEvent event) {
|
||||
return target
|
||||
.path("postEvent")
|
||||
.request()
|
||||
.buildPost(Entity.entity(event, CloudEventsProvider.CLOUDEVENT_TYPE))
|
||||
.invoke();
|
||||
}
|
||||
|
||||
public Response sendEventAsStructured(WebTarget target, CloudEvent event) {
|
||||
return target
|
||||
.path("postEvent")
|
||||
.request()
|
||||
.buildPost(Entity.entity(event, "application/cloudevents+json"))
|
||||
.invoke();
|
||||
}
|
||||
|
||||
public CloudEvent getEvent(WebTarget target) {
|
||||
Response response = target
|
||||
.path("getEvent")
|
||||
.request()
|
||||
.buildGet()
|
||||
.invoke();
|
||||
|
||||
return response.readEntity(CloudEvent.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating EE 8 applications to EE 9+
|
||||
The main change between Jakarta EE 8 and Jakarta EE 9 and future versions is the changing of the `javax.` to `jakarta.` namespaces used by key packages such as `jakarta.ws.rs-api` which provides the restful-ws API.
|
||||
|
||||
This change largely impacts only `import` statements it does filter down to dependencies such as this.
|
||||
|
||||
### Application migration
|
||||
For application migration we would recommend reviewing materials available from https://jakarta.ee/resources/#documentation as a starting point.
|
||||
|
||||
### CloudEvents Dependency
|
||||
To migrate to use EE 9+ supported package - replace `cloudevents-http-restful-ws` with `cloudevents-http-restful-ws-jakarta` and ensure the version is a minimum of `2.5.0-SNAPSHOT`
|
||||
|
||||
## Examples
|
||||
|
||||
- [Microprofile and Liberty](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-micropofile-liberty)
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
title: CloudEvents HTTP Jakarta RESTful Web Services
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# HTTP Protocol Binding for Jakarta EE8 - RESTful Web Services
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Jakarta
|
||||
RESTful Web Services Binding for Jakarta EE 8:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
This integration is tested with Jersey, RestEasy & Spring Boot Jersey.
|
||||
|
||||
## Receiving CloudEvents
|
||||
|
||||
You need to configure the `CloudEventsProvider` to enable
|
||||
marshalling/unmarshalling of CloudEvents.
|
||||
|
||||
Below is a sample on how to read and write CloudEvents:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
@Path("/")
|
||||
public class EventReceiverResource {
|
||||
|
||||
@GET
|
||||
@Path("getMinEvent")
|
||||
public CloudEvent getMinEvent() {
|
||||
return CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Return the CloudEvent using the HTTP binding structured encoding
|
||||
@GET
|
||||
@Path("getStructuredEvent")
|
||||
@StructuredEncoding("application/cloudevents+csv")
|
||||
public CloudEvent getStructuredEvent() {
|
||||
return CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("postEventWithoutBody")
|
||||
public Response postEvent(CloudEvent inputEvent) {
|
||||
// Handle the event
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending CloudEvents
|
||||
|
||||
You need to configure the `CloudEventsProvider` to enable
|
||||
marshalling/unmarshalling of CloudEvents.
|
||||
|
||||
Below is a sample on how to use the client to send a CloudEvent:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.http.restful.ws.CloudEventsProvider;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.client.WebTarget;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
public class CloudEventSender {
|
||||
|
||||
public Response sendEvent(WebTarget target, CloudEvent event) {
|
||||
return target
|
||||
.path("postEvent")
|
||||
.request()
|
||||
.buildPost(Entity.entity(event, CloudEventsProvider.CLOUDEVENT_TYPE))
|
||||
.invoke();
|
||||
}
|
||||
|
||||
public Response sendEventAsStructured(WebTarget target, CloudEvent event) {
|
||||
return target
|
||||
.path("postEvent")
|
||||
.request()
|
||||
.buildPost(Entity.entity(event, "application/cloudevents+json"))
|
||||
.invoke();
|
||||
}
|
||||
|
||||
public CloudEvent getEvent(WebTarget target) {
|
||||
Response response = target
|
||||
.path("getEvent")
|
||||
.request()
|
||||
.buildGet()
|
||||
.invoke();
|
||||
|
||||
return response.readEntity(CloudEvent.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
- [Quarkus and Resteasy](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-quarkus)
|
||||
- [Jersey and Spring Boot](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-spring-boot)
|
|
@ -0,0 +1,98 @@
|
|||
---
|
||||
title: CloudEvents HTTP Vert.x
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# HTTP Protocol Binding for Eclipse Vert.x
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-http-vertx)
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Vertx
|
||||
HTTP Transport:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-vertx</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Receiving CloudEvents
|
||||
|
||||
Assuming you have in classpath [`cloudevents-json-jackson`](json-jackson.md),
|
||||
below is a sample on how to read and write CloudEvents:
|
||||
|
||||
```java
|
||||
|
||||
import io.cloudevents.http.vertx.VertxMessageFactory;
|
||||
import io.cloudevents.core.message.StructuredMessageReader;
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
|
||||
public class CloudEventServerVerticle extends AbstractVerticle {
|
||||
|
||||
public void start() {
|
||||
vertx.createHttpServer()
|
||||
.requestHandler(req -> {
|
||||
VertxMessageFactory.createReader(req)
|
||||
.onSuccess(messageReader -> {
|
||||
CloudEvent event = messageReader.toEvent();
|
||||
// Echo the message, as structured mode
|
||||
VertxMessageFactory
|
||||
.createWriter(req.response())
|
||||
.writeStructured(event, "application/cloudevents+json");
|
||||
})
|
||||
.onFailure(t -> req.response().setStatusCode(500).end());
|
||||
})
|
||||
.listen(8080)
|
||||
.onSuccess(server ->
|
||||
System.out.println("Server started on port " + server.actualPort())
|
||||
).onFailure(t -> {
|
||||
System.out.println("Error starting the server");
|
||||
serverResult.cause().printStackTrace();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending CloudEvents
|
||||
|
||||
Below is a sample on how to use the client to send and receive a CloudEvent:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.http.vertx.VertxMessageFactory;
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
import io.vertx.ext.web.client.WebClient;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
public class CloudEventClientVerticle extends AbstractVerticle {
|
||||
|
||||
public void start() {
|
||||
WebClient client = WebClient.create(vertx);
|
||||
|
||||
CloudEvent reqEvent = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
|
||||
VertxMessageFactory
|
||||
.createWriter(client.postAbs("http://localhost:8080"))
|
||||
.writeBinary(reqEvent)
|
||||
.onSuccess(response -> {
|
||||
CloudEvent responseEvent = VertxMessageFactory
|
||||
.createReader(response)
|
||||
.toEvent();
|
||||
})
|
||||
.onFailure(Throwable::printStackTrace);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples:
|
||||
|
||||
- [Vert.x Client and Server](https://github.com/cloudevents/sdk-java/tree/main/examples/vertx)
|
181
docs/index.md
181
docs/index.md
|
@ -5,100 +5,133 @@ nav_order: 1
|
|||
|
||||
# Java SDK for CloudEvents
|
||||
|
||||
A Java API for the [CloudEvents specification](https://github.com/cloudevents/spec)
|
||||
|
||||
1. [Supported Specification Features](#supported-specification-features)
|
||||
1. [Modules](#modules)
|
||||
1. [Introduction](#introduction)
|
||||
1. [Supported features](#supported-features)
|
||||
1. [Get Started](#get-started)
|
||||
1. [Use an event format](#use-an-event-format)
|
||||
1. [Use a protocol binding](#use-a-protocol-binding)
|
||||
1. [Modules](#modules)
|
||||
|
||||
## Supported Specification Features
|
||||
## Introduction
|
||||
|
||||
Supported features of the specification:
|
||||
The Java SDK for CloudEvents is a collection of Java libraries to adopt
|
||||
CloudEvents in your Java application.
|
||||
|
||||
| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) |
|
||||
| -------- | -- | -- |
|
||||
| CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| AMQP Protocol Binding | :x: | :x: |
|
||||
| AVRO Event Format | :x: | :x: |
|
||||
| HTTP Protocol Binding | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Vert.x](https://github.com/cloudevents/sdk-java/tree/master/http/vertx) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jakarta Restful WS](https://github.com/cloudevents/sdk-java/tree/master/http/restful-ws) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jackson](https://github.com/cloudevents/sdk-java/tree/master/formats/json-jackson) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| [Kafka Protocol Binding](https://github.com/cloudevents/sdk-java/tree/master/kafka) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
Using the Java SDK you can:
|
||||
|
||||
## Modules
|
||||
- Access, create and manipulate `CloudEvent` inside your application.
|
||||
- Serialize and deserialize `CloudEvent` back and forth using the _CloudEvents
|
||||
Event Format_, like Json.
|
||||
- Read and write `CloudEvent` back and forth to HTTP, Kafka, AMQP using the
|
||||
_CloudEvents Protocol Binding_ implementations we provide for a wide range
|
||||
of well known Java frameworks/libraries.
|
||||
|
||||
The CloudEvents SDK for Java is composed by several modules, each one providing a different feature from the different sub specs of [CloudEvents specification](#supported-specification-features):
|
||||
## Supported features
|
||||
|
||||
* [`cloudevents-api`] Module providing the `CloudEvent` and other base interfaces
|
||||
* [`cloudevents-core`] Module providing `CloudEvent` implementation, `CloudEventBuilder` to create `CloudEvent`s programmatically, `EventFormat` to implement [Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format), `Message`/`MessageVisitor` to implement [Protocol bindings](https://github.com/cloudevents/spec/blob/v1.0/spec.md#protocol-binding)
|
||||
* [`cloudevents-json-jackson`] Implementation of [JSON Event format] with [Jackson](https://github.com/FasterXML/jackson)
|
||||
* [`cloudevents-http-vertx`] Implementation of [HTTP Protocol Binding] with [Vert.x Core](https://vertx.io/)
|
||||
* [`cloudevents-http-restful-ws`] Implementation of [HTTP Protocol Binding] for [Jakarta Restful WS](https://jakarta.ee/specifications/restful-ws/)
|
||||
* [`cloudevents-kafka`] Implementation of [Kafka Protocol Binding]
|
||||
| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) |
|
||||
| :------------------------------------------------: | :---------------------------------------------------: | :---------------------------------------------------: |
|
||||
| CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| AMQP Protocol Binding | :x: | :x: |
|
||||
| - [Proton](amqp-proton.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| AVRO Event Format | :x: | :x: |
|
||||
| HTTP Protocol Binding | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Vert.x](http-vertx.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jakarta Restful WS](http-jakarta-restful-ws.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Basic](http-basic.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Spring](spring.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [http4k][http4k]<sup>†</sup> | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jackson](json-jackson.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Proto](protobuf.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| XML Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [XML](xml.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| [Kafka Protocol Binding](kafka.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
|
||||
You can look at the latest published artifacts on [Maven Central](https://search.maven.org/search?q=g:io.cloudevents).
|
||||
<sub>† Source/artifacts hosted externally</sub>
|
||||
|
||||
## Get Started
|
||||
|
||||
You can start creating events using the `CloudEventBuilder`:
|
||||
In order to start learning how to create, access and manipulate `CloudEvent`s,
|
||||
check out the [Core module documentation](core.md).
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import java.net.URI;
|
||||
If you want to serialize and deserialize events and data back and forth to JSON,
|
||||
check out the [Jackson Json module documentation](json-jackson.md).
|
||||
|
||||
final CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("000")
|
||||
.withType("example.demo")
|
||||
.withSource(URI.create("http://example.com"))
|
||||
.withData("application/json", "{}".getBytes())
|
||||
.build();
|
||||
```
|
||||
Depending on the protocol and framework you're using, if you want to send and
|
||||
receive CloudEvents, check out the dedicated pages:
|
||||
|
||||
Look at [`cloudevents-core`] README for more information.
|
||||
- [AMQP using Proton](amqp-proton.md)
|
||||
- [HTTP using Vert.x](http-vertx.md)
|
||||
- [HTTP using Jakarta EE 8 - Jakarta Restful WS](http-jakarta-restful-ws.md)
|
||||
- [HTTP using Jakarta EE 9+ - Jakarta Restful WS](http-jakarta-restful-ws-jakarta.md)
|
||||
- [HTTP using Spring](spring.md)
|
||||
- [HTTP using Jackson](json-jackson.md)
|
||||
- [Kafka](kafka.md)
|
||||
|
||||
## Use an event format
|
||||
If you're interested in implementing an object conforming to the `CloudEvent`
|
||||
and related interfaces, in order to interoperate with the other components of
|
||||
the SDK, check out the [API module documentation](api.md).
|
||||
|
||||
Event formats implementations are auto-discovered through an SPI. You can use them accessing through `EventFormatProvider`.
|
||||
For example, given you have in your classpath the [`cloudevents-json-jackson`] module, you can serialize/deserialize an event to/from JSON using:
|
||||
You can also check out the
|
||||
[**Examples**](https://github.com/cloudevents/sdk-java/tree/main/examples).
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.format.EventFormatProvider;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
## Modules
|
||||
|
||||
EventFormat format = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(JsonFormat.CONTENT_TYPE);
|
||||
The CloudEvents SDK for Java is composed by several modules, each one providing
|
||||
a different feature from the different sub specs of
|
||||
[CloudEvents specification](#supported-features):
|
||||
|
||||
// Serialize event
|
||||
byte[] serialized = format.serialize(event);
|
||||
- [`cloudevents-api`] Module providing the `CloudEvent` and other base
|
||||
interfaces
|
||||
- [`cloudevents-core`] Module providing `CloudEvent` implementation,
|
||||
`CloudEventBuilder` to create `CloudEvent`s programmatically, `EventFormat`
|
||||
to implement
|
||||
[Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format),
|
||||
`MessageReader` /`MessageWriter` to implement
|
||||
[Protocol bindings](https://github.com/cloudevents/spec/blob/v1.0/spec.md#protocol-binding)
|
||||
- [`cloudevents-bom`] Module providing a
|
||||
[bill of materials (BOM)](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#bill-of-materials-bom-poms)
|
||||
for easier integration of CloudEvents in other projects
|
||||
- [`cloudevents-json-jackson`] Implementation of [JSON Event format] with
|
||||
[Jackson](https://github.com/FasterXML/jackson)
|
||||
- [`cloudevents-protobuf`] Implementation of [Protobuf Event format] using code generated
|
||||
from the standard [protoc](https://github.com/protocolbuffers/protobuf) compiler.
|
||||
- [`cloudevents-xml`] Implementation of the XML Event Format.
|
||||
- [`cloudevents-http-vertx`] Implementation of [HTTP Protocol Binding] with
|
||||
[Vert.x Core](https://vertx.io/)
|
||||
- [`cloudevents-http-restful-ws`] Implementation of [HTTP Protocol Binding]
|
||||
for [Jakarta EE 8 Restful WS](https://jakarta.ee/specifications/restful-ws/2.1/)
|
||||
- [`cloudevents-http-restful-ws-jakarta`] Implementation of [HTTP Protocol Binding]
|
||||
for [Jakarta EE 9+ Restful WS](https://jakarta.ee/specifications/restful-ws/)
|
||||
- [`cloudevents-http-basic`] Generic implementation of [HTTP Protocol
|
||||
Binding], primarily intended for integrators
|
||||
- [`cloudevents-kafka`] Implementation of [Kafka Protocol Binding]
|
||||
- [`cloudevents-amqp-proton`] Implementation of [AMQP Protocol Binding] with
|
||||
[Proton](http://qpid.apache.org/proton/)
|
||||
- [`cloudevents-spring`] Integration of `CloudEvent` with different Spring
|
||||
APIs, like MVC, WebFlux and Messaging
|
||||
|
||||
// Deserialize event
|
||||
CloudEvent event = format.deserialize(bytes);
|
||||
```
|
||||
You can look at the latest published artifacts on
|
||||
[Maven Central](https://search.maven.org/search?q=g:io.cloudevents).
|
||||
|
||||
## Use a protocol binding
|
||||
|
||||
Each protocol binding has its own APIs, depending on the library/framework it's integrating with.
|
||||
Check out the documentation of the protocol binding modules:
|
||||
|
||||
* [`cloudevents-http-vertx`]
|
||||
* [`cloudevents-http-restful-ws`]
|
||||
* [`cloudevents-kafka`]
|
||||
|
||||
[JSON Event Format]: https://github.com/cloudevents/spec/blob/v1.0/json-format.md
|
||||
[JSON Event format]: https://github.com/cloudevents/spec/blob/v1.0/json-format.md
|
||||
[Protobuf Event format]: https://github.com/cloudevents/spec/blob/v1.0.1/protobuf-format.md
|
||||
[HTTP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md
|
||||
[Kafka Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/kafka-protocol-binding.md
|
||||
[`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/master/api
|
||||
[`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/master/core
|
||||
[`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/master/formats/json-jackson
|
||||
[`cloudevents-http-vertx`]: https://github.com/cloudevents/sdk-java/tree/master/http/vertx
|
||||
[`cloudevents-http-restful-ws`]: https://github.com/cloudevents/sdk-java/tree/master/http/restful-ws
|
||||
[`cloudevents-kafka`]: https://github.com/cloudevents/sdk-java/tree/master/kafka
|
||||
[AMQP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/amqp-protocol-binding.md
|
||||
[`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/main/api
|
||||
[`cloudevents-bom`]: https://github.com/cloudevents/sdk-java/tree/main/bom
|
||||
[`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/main/core
|
||||
[`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/main/formats/json-jackson
|
||||
[`cloudevents-protobuf`]: https://github.com/cloudevents/sdk-java/tree/main/formats/protobuf
|
||||
[`cloudevents-xml`]: https://github.com/cloudevents/sdk-java/tree/main/formats/xml
|
||||
[`cloudevents-http-vertx`]: https://github.com/cloudevents/sdk-java/tree/main/http/vertx
|
||||
[`cloudevents-http-basic`]: https://github.com/cloudevents/sdk-java/tree/main/http/basic
|
||||
[`cloudevents-http-restful-ws`]: https://github.com/cloudevents/sdk-java/tree/main/http/restful-ws
|
||||
[`cloudevents-http-restful-ws-jakarta`]: https://github.com/cloudevents/sdk-java/tree/main/http/restful-ws-jakarta
|
||||
[`cloudevents-kafka`]: https://github.com/cloudevents/sdk-java/tree/main/kafka
|
||||
[`cloudevents-amqp-proton`]: https://github.com/cloudevents/sdk-java/tree/main/amqp
|
||||
[`cloudevents-spring`]: https://github.com/cloudevents/sdk-java/tree/main/spring
|
||||
[http4k]: https://www.http4k.org/guide/modules/cloud_events/
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
---
|
||||
title: CloudEvents Json Jackson
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||
# CloudEvents Json Jackson
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
|
||||
|
||||
This module provides the JSON `EventFormat` implementation using Jackson and a
|
||||
`PojoCloudEventDataMapper` to convert `CloudEventData` to POJOs using the
|
||||
Jackson `ObjectMapper`.
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-json-jackson</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Using the JSON Event Format
|
||||
|
||||
You don't need to perform any operation to configure the module, more than
|
||||
adding the dependency to your project:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.format.ContentType;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
|
||||
byte[]serialized = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(ContentType.JSON)
|
||||
.serialize(event);
|
||||
```
|
||||
|
||||
The `EventFormatProvider` will resolve automatically the `JsonFormat` using the
|
||||
`ServiceLoader` APIs.
|
||||
|
||||
## Mapping `CloudEventData` to POJOs using Jackson `ObjectMapper`
|
||||
|
||||
Using the Jackson `ObjectMapper`, you can easily extract a POJO starting from
|
||||
any `CloudEventData`:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.data.PojoCloudEventData;
|
||||
import io.cloudevents.jackson.PojoCloudEventDataMapper;
|
||||
|
||||
import static io.cloudevents.core.CloudEventUtils.mapData;
|
||||
|
||||
PojoCloudEventData<User> cloudEventData = mapData(
|
||||
inputEvent,
|
||||
PojoCloudEventDataMapper.from(objectMapper,User.class)
|
||||
);
|
||||
// check if cloudEventData is null
|
||||
User user = cloudEventData.getValue();
|
||||
```
|
|
@ -0,0 +1,122 @@
|
|||
---
|
||||
title: CloudEvents Kafka
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# CloudEvents Kafka
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-kafka)
|
||||
|
||||
Implementation of Kafka Protocol Binding to send and receive CloudEvents.
|
||||
|
||||
For Maven based projects, use the following to configure the
|
||||
[Kafka Protocol Binding](https://github.com/cloudevents/spec/blob/main/kafka-protocol-binding.md):
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
### Producing CloudEvents
|
||||
|
||||
To produce CloudEvents in Kafka, configure the KafkaProducer to use the provided
|
||||
`CloudEventSerializer`:
|
||||
|
||||
```java
|
||||
import java.util.Properties;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.kafka.CloudEventSerializer;
|
||||
import org.apache.kafka.clients.producer.KafkaProducer;
|
||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
|
||||
public class CloudEventProducer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Properties props = new Properties();
|
||||
|
||||
// Other config props
|
||||
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
|
||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CloudEventSerializer.class);
|
||||
|
||||
try (KafkaProducer<String, CloudEvent> producer = new KafkaProducer<>(props)) {
|
||||
|
||||
// Build an event
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.kafka")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
|
||||
// Produce the event
|
||||
producer.send(new ProducerRecord<>("your.topic", event));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You can configure the Encoding and EventFormat to use to emit the event.
|
||||
|
||||
Check out the
|
||||
[`CloudEventSerializer`](https://github.com/cloudevents/sdk-java/tree/main/kafka/src/main/java/io/cloudevents/kafka/CloudEventSerializer.java)
|
||||
javadoc for more info.
|
||||
|
||||
### Partition key extension
|
||||
|
||||
If you want your producer to use the `partitionkey` extension, you can use the
|
||||
PartitionKeyExtensionInterceptor.
|
||||
|
||||
```java
|
||||
producerProps.put(
|
||||
ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,
|
||||
io.cloudevents.kafka.PartitionKeyExtensionInterceptor.class
|
||||
);
|
||||
```
|
||||
|
||||
When using in your producer, this interceptor will pick the `partitionkey`
|
||||
extension from the event and will set it as record key, regardless of the input record key.
|
||||
Check out the [`PartitionKeyExtensionInterceptor`](https://github.com/cloudevents/sdk-java/tree/main/kafka/src/main/java/io/cloudevents/kafka/PartitionKeyExtensionInterceptor.java)
|
||||
javadoc for more info.
|
||||
|
||||
## Consuming CloudEvents
|
||||
|
||||
To consume CloudEvents in Kafka, configure the KafkaConsumer to use the provided
|
||||
`CloudEventDeserializer`:
|
||||
|
||||
```java
|
||||
import java.time.Duration;
|
||||
import java.util.Properties;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.kafka.CloudEventDeserializer;
|
||||
import org.apache.kafka.clients.consumer.ConsumerConfig;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecords;
|
||||
import org.apache.kafka.clients.consumer.KafkaConsumer;
|
||||
import org.apache.kafka.common.serialization.StringDeserializer;
|
||||
|
||||
public class CloudEventConsumer {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Properties props = new Properties();
|
||||
|
||||
// Other config props
|
||||
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class);
|
||||
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, CloudEventDeserializer.class);
|
||||
|
||||
try (KafkaConsumer<String, CloudEvent> consumer = new KafkaConsumer<>(props)) {
|
||||
|
||||
ConsumerRecords<String, CloudEvent> records = consumer.poll(Duration.ofSeconds(10));
|
||||
|
||||
records.forEach(rec -> {
|
||||
System.out.println(rec.value().toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: CloudEvents Protocol Buffers
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||
# CloudEvents Protocol Buffers
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
|
||||
|
||||
This module provides the Protocol Buffer (protobuf) `EventFormat` implementation using the Java
|
||||
Protobuf runtime and classes generated from the CloudEvents
|
||||
[proto spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.proto).
|
||||
|
||||
# Setup
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-protobuf</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
No further configuration is required is use the module.
|
||||
|
||||
## Using the Protobuf Event Format
|
||||
|
||||
### Event serialization
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.format.ContentType;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
|
||||
byte[]serialized = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(ContentType.PROTO)
|
||||
.serialize(event);
|
||||
```
|
||||
|
||||
The `EventFormatProvider` will automatically resolve the `ProtobufFormat` using the
|
||||
`ServiceLoader` APIs.
|
||||
|
||||
## Passing Protobuf messages as CloudEvent data.
|
||||
|
||||
The `ProtoCloudEventData` capability provides a convenience mechanism to handle Protobuf message object data.
|
||||
|
||||
### Building
|
||||
|
||||
```java
|
||||
// Build my business event message.
|
||||
com.google.protobuf.Message myMessage = ..... ;
|
||||
|
||||
// Wrap the protobuf message as CloudEventData.
|
||||
CloudEventData ceData = ProtoCloudEventData.wrap(myMessage);
|
||||
|
||||
// Build the CloudEvent
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.protodata")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withData(ceData)
|
||||
.build();
|
||||
```
|
||||
|
||||
### Reading
|
||||
|
||||
If the `ProtobufFormat` is used to deserialize a CloudEvent that contains a protobuf message object as data you can use
|
||||
the `ProtoCloudEventData` to access it as an 'Any' directly.
|
||||
|
||||
```java
|
||||
|
||||
// Deserialize the event.
|
||||
CloudEvent myEvent = eventFormat.deserialize(raw);
|
||||
|
||||
// Get the Data
|
||||
CloudEventData eventData = myEvent.getData();
|
||||
|
||||
if (ceData instanceOf ProtoCloudEventData) {
|
||||
|
||||
// Obtain the protobuf 'any'
|
||||
Any anAny = ((ProtoCloudEventData) eventData).getAny();
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue