Compare commits
118 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 |
|
@ -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:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- main
|
||||||
|
- '[0-9]+.[0-9]+'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
@ -11,7 +13,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [ 8, 11 ]
|
java: [ 8, 11, 17 ]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup java
|
- name: Setup java
|
||||||
|
@ -19,8 +21,9 @@ jobs:
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
- run: |
|
- run: |
|
||||||
mvn clean install -DskipTests -B
|
./mvnw clean install -DskipTests -B
|
||||||
mvn verify -B
|
./mvnw verify -B
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Deploy
|
name: Deploy
|
||||||
|
@ -31,13 +34,13 @@ jobs:
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v1
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
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-username: MAVEN_USERNAME # env variable for username in deploy
|
||||||
server-password: MAVEN_CENTRAL_TOKEN # env variable for token 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-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
|
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
|
||||||
- name: Publish to Apache Maven Central
|
- name: Publish to Apache Maven Central
|
||||||
run: mvn clean deploy -Drelease -DskipTests -B
|
run: ./mvnw clean deploy -Drelease -DskipTests -B
|
||||||
env:
|
env:
|
||||||
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||||
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
|
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
|
|
@ -5,6 +5,14 @@ on:
|
||||||
version:
|
version:
|
||||||
description: 'Version to bump (without prepending "v")'
|
description: 'Version to bump (without prepending "v")'
|
||||||
required: true
|
required: true
|
||||||
|
maven-modules:
|
||||||
|
description: "Whether to bump versions in pom.xml files"
|
||||||
|
type: choice
|
||||||
|
required: true
|
||||||
|
default: 'true'
|
||||||
|
options:
|
||||||
|
- 'true'
|
||||||
|
- 'false'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
bump:
|
bump:
|
||||||
|
@ -19,7 +27,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
java-version: 8
|
java-version: 8
|
||||||
- name: Bump version using Maven
|
- 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
|
- name: Bump version in docs
|
||||||
if: ${{ !endsWith(github.event.inputs.version, 'SNAPSHOT') }}
|
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" {} +'
|
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:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
|
- main
|
||||||
|
- '[0-9]+.[0-9]+'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
java: [ 8, 11 ]
|
java: [ 8, 11, 17 ]
|
||||||
name: Java ${{ matrix.java }} Test
|
name: Java ${{ matrix.java }} Test
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Setup java
|
- name: Setup java
|
||||||
uses: actions/setup-java@v1
|
uses: actions/setup-java@v2
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.java }}
|
java-version: ${{ matrix.java }}
|
||||||
|
distribution: 'temurin'
|
||||||
- run: |
|
- run: |
|
||||||
mvn clean install -DskipTests -B
|
./mvnw clean install -DskipTests -B
|
||||||
mvn verify -B
|
./mvnw verify -B
|
||||||
|
./mvnw javadoc:javadoc
|
||||||
|
|
|
@ -45,3 +45,5 @@ _site/
|
||||||
|
|
||||||
# MacOS
|
# MacOS
|
||||||
*.DS_Store
|
*.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
|
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
|
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)
|
* [Getting Started](#getting-started)
|
||||||
* [Branches](#branches)
|
* [Branches](#branches)
|
||||||
* [Commit Messages](#commit-messages)
|
* [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)
|
* [Submitting and Updating a Pull Request](#submitting-and-updating-a-pull-request)
|
||||||
* [Congratulations!](#congratulations)
|
* [Congratulations!](#congratulations)
|
||||||
|
|
||||||
|
@ -32,7 +47,7 @@ organized.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
git reset --hard upstream/master
|
git reset --hard upstream/main
|
||||||
git checkout FETCH_HEAD
|
git checkout FETCH_HEAD
|
||||||
git checkout -b 48-fix-http-agent-error
|
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
|
Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will
|
||||||
be rejected by the automated DCO check.
|
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
|
submitting your pull request, be sure that your branch has been updated
|
||||||
with the latest commits.
|
with the latest commits.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
git fetch upstream
|
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
|
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
|
exist and what files need attention. Resolve the conflicts in each file, then
|
||||||
continue with the rebase with `git rebase --continue`.
|
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
|
Before submitting a pull request, you should make sure that all of the tests
|
||||||
successfully pass.
|
successfully pass.
|
||||||
|
|
||||||
Once you have sent your pull request, `master` may continue to evolve
|
Once you have sent your pull request, `main` may continue to evolve
|
||||||
before your pull request has landed. If there are any commits on `master`
|
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
|
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
|
these changes before the pull request can land. Resolve conflicts the same
|
||||||
way as before.
|
way as before.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
git fetch upstream
|
git fetch upstream
|
||||||
git rebase upstream/master
|
git rebase upstream/main
|
||||||
# fix any potential conflicts
|
# fix any potential conflicts
|
||||||
git push -f origin 48-fix-http-agent-error
|
git push -f origin 48-fix-http-agent-error
|
||||||
```
|
```
|
||||||
|
@ -141,7 +156,7 @@ for details.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
git commit -m "fixup: fix typo"
|
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.
|
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
|
# 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
|
## Tips
|
||||||
|
|
||||||
Here are a few tips for repository maintainers.
|
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
|
## 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
|
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
|
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 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
|
release commit, and a new branch `v1.x.y` for subsequent minor and patch
|
||||||
level releases of that major version. However, development will continue
|
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 only created for each major version. Minor and patch level releases
|
||||||
are simply tagged.
|
are simply tagged.
|
24
README.md
24
README.md
|
@ -63,8 +63,10 @@ Javadocs are available on [javadoc.io](https://www.javadoc.io):
|
||||||
|
|
||||||
- [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
- [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||||
- [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
- [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-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
|
||||||
- [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
|
- [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-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-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-http-vertx](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-vertx)
|
||||||
|
@ -97,13 +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
|
Each SDK may have its own unique processes, tooling and guidelines, common
|
||||||
governance related material can be found in the
|
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
|
directory. In particular, in there you will find information concerning how SDK
|
||||||
projects are
|
projects are
|
||||||
[managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md),
|
[managed](https://github.com/cloudevents/spec/blob/main/docs/SDK-GOVERNANCE.md),
|
||||||
[guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md)
|
[guidelines](https://github.com/cloudevents/spec/blob/main/docs/SDK-maintainer-guidelines.md)
|
||||||
for how PR reviews and approval, and our
|
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.
|
information.
|
||||||
|
|
||||||
[http4k]: https://www.http4k.org/guide/modules/cloud_events/
|
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.
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-parent</artifactId>
|
<artifactId>cloudevents-parent</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<protonj.version>0.33.7</protonj.version>
|
<protonj.version>0.34.1</protonj.version>
|
||||||
<jsr305.version>3.0.2</jsr305.version>
|
<jsr305.version>3.0.2</jsr305.version>
|
||||||
<module-name>io.cloudevents.amqp.proton</module-name>
|
<module-name>io.cloudevents.amqp.proton</module-name>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-parent</artifactId>
|
<artifactId>cloudevents-parent</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>cloudevents-api</artifactId>
|
<artifactId>cloudevents-api</artifactId>
|
||||||
|
|
|
@ -216,4 +216,15 @@ public class CloudEventRWException extends RuntimeException {
|
||||||
cause
|
cause
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,12 @@ public interface CloudEventReader {
|
||||||
/**
|
/**
|
||||||
* Like {@link #read(CloudEventWriterFactory, CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
* 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)
|
* @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 <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException {
|
default <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException {
|
||||||
return read(writerFactory, CloudEventDataMapper.identity());
|
return read(writerFactory, CloudEventDataMapper.identity());
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-parent</artifactId>
|
<artifactId>cloudevents-parent</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>cloudevents-benchmarks</artifactId>
|
<artifactId>cloudevents-benchmarks</artifactId>
|
||||||
|
@ -59,6 +59,11 @@
|
||||||
<artifactId>cloudevents-kafka</artifactId>
|
<artifactId>cloudevents-kafka</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<artifactId>cloudevents-sql</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openjdk.jmh</groupId>
|
<groupId>org.openjdk.jmh</groupId>
|
||||||
|
@ -136,7 +141,7 @@
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-install-plugin</artifactId>
|
<artifactId>maven-install-plugin</artifactId>
|
||||||
<version>2.5.1</version>
|
<version>3.1.3</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-jar-plugin</artifactId>
|
<artifactId>maven-jar-plugin</artifactId>
|
||||||
|
@ -156,7 +161,7 @@
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-source-plugin</artifactId>
|
<artifactId>maven-source-plugin</artifactId>
|
||||||
<version>2.2.1</version>
|
<version>3.3.0</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<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>
|
|
@ -22,7 +22,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-parent</artifactId>
|
<artifactId>cloudevents-parent</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>cloudevents-core</artifactId>
|
<artifactId>cloudevents-core</artifactId>
|
||||||
|
|
|
@ -26,7 +26,7 @@ import java.util.*;
|
||||||
/**
|
/**
|
||||||
* This extension embeds context from Distributed Tracing so that distributed systems can include traces that span an event-driven system.
|
* 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 CloudEventExtension {
|
public final class DistributedTracingExtension implements CloudEventExtension {
|
||||||
|
|
||||||
|
|
|
@ -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(); }
|
||||||
|
|
||||||
|
}
|
|
@ -124,6 +124,9 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
||||||
return self;
|
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) {
|
public SELF withExtension(@Nonnull String key, @Nonnull Number value) {
|
||||||
if (!isValidExtensionName(key)) {
|
if (!isValidExtensionName(key)) {
|
||||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||||
|
@ -132,6 +135,14 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
||||||
return self;
|
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) {
|
public SELF withExtension(@Nonnull String key, @Nonnull Boolean value) {
|
||||||
if (!isValidExtensionName(key)) {
|
if (!isValidExtensionName(key)) {
|
||||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||||
|
@ -208,12 +219,16 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
||||||
return new IllegalStateException("Attribute '" + attributeName + "' cannot be null");
|
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.
|
* Validates the extension name as defined in CloudEvents spec.
|
||||||
*
|
*
|
||||||
* @param name the extension name
|
* @param name the extension name
|
||||||
* @return true if extension name is valid, false otherwise
|
* @return true if extension name is valid, false otherwise
|
||||||
* @see <a href="https://github.com/cloudevents/spec/blob/master/spec.md#attribute-naming-convention">attribute-naming-convention</a>
|
* @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) {
|
private static boolean isValidExtensionName(String name) {
|
||||||
for (int i = 0; i < name.length(); i++) {
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,6 +56,9 @@ public abstract class BaseGenericBinaryMessageReaderImpl<HK, HV> extends BaseBin
|
||||||
// This implementation avoids to use visitAttributes and visitExtensions
|
// This implementation avoids to use visitAttributes and visitExtensions
|
||||||
// in order to complete the visit in one loop
|
// in order to complete the visit in one loop
|
||||||
this.forEachHeader((key, value) -> {
|
this.forEachHeader((key, value) -> {
|
||||||
|
if (value == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (isContentTypeHeader(key)) {
|
if (isContentTypeHeader(key)) {
|
||||||
visitor.withContextAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
|
visitor.withContextAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
|
||||||
} else if (isCloudEventsHeader(key)) {
|
} else if (isCloudEventsHeader(key)) {
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
import javax.annotation.ParametersAreNonnullByDefault;
|
||||||
|
|
||||||
|
import io.cloudevents.core.format.ContentType;
|
||||||
import io.cloudevents.core.format.EventFormat;
|
import io.cloudevents.core.format.EventFormat;
|
||||||
import io.cloudevents.lang.Nullable;
|
import io.cloudevents.lang.Nullable;
|
||||||
|
|
||||||
|
@ -98,4 +99,14 @@ public final class EventFormatProvider {
|
||||||
return this.formats.get(contentType);
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,12 @@
|
||||||
*/
|
*/
|
||||||
package io.cloudevents.core.v03;
|
package io.cloudevents.core.v03;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.SpecVersion;
|
import io.cloudevents.SpecVersion;
|
||||||
import io.cloudevents.core.CloudEventUtils;
|
import io.cloudevents.core.CloudEventUtils;
|
||||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
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.CloudEventContextReader;
|
||||||
import io.cloudevents.rw.CloudEventContextWriter;
|
import io.cloudevents.rw.CloudEventContextWriter;
|
||||||
import io.cloudevents.rw.CloudEventRWException;
|
import io.cloudevents.rw.CloudEventRWException;
|
||||||
|
@ -121,8 +124,15 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
throw createMissingAttributeException("type");
|
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
|
@Override
|
||||||
|
|
|
@ -21,6 +21,8 @@ import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.SpecVersion;
|
import io.cloudevents.SpecVersion;
|
||||||
import io.cloudevents.core.CloudEventUtils;
|
import io.cloudevents.core.CloudEventUtils;
|
||||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
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.CloudEventContextReader;
|
||||||
import io.cloudevents.rw.CloudEventContextWriter;
|
import io.cloudevents.rw.CloudEventContextWriter;
|
||||||
import io.cloudevents.rw.CloudEventRWException;
|
import io.cloudevents.rw.CloudEventRWException;
|
||||||
|
@ -118,8 +120,16 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
||||||
if (type == null) {
|
if (type == null) {
|
||||||
throw createMissingAttributeException(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
|
@Override
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/*
|
||||||
|
* 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.validator;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
package io.cloudevents.core.data;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
@ -8,7 +10,7 @@ class PojoCloudEventDataTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testWrapAndMemoization() {
|
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())
|
assertThat(data.getValue())
|
||||||
.isEqualTo(10);
|
.isEqualTo(10);
|
||||||
|
@ -16,7 +18,7 @@ class PojoCloudEventDataTest {
|
||||||
byte[] firstConversion = data.toBytes();
|
byte[] firstConversion = data.toBytes();
|
||||||
|
|
||||||
assertThat(firstConversion)
|
assertThat(firstConversion)
|
||||||
.isEqualTo("10".getBytes());
|
.isEqualTo("10".getBytes(StandardCharsets.UTF_8));
|
||||||
|
|
||||||
assertThat(data.toBytes())
|
assertThat(data.toBytes())
|
||||||
.isSameAs(firstConversion);
|
.isSameAs(firstConversion);
|
||||||
|
@ -24,7 +26,7 @@ class PojoCloudEventDataTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testAlreadySerializedValue() {
|
void testAlreadySerializedValue() {
|
||||||
byte[] serialized = "10".getBytes();
|
byte[] serialized = "10".getBytes(StandardCharsets.UTF_8);
|
||||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, v -> serialized);
|
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, v -> serialized);
|
||||||
|
|
||||||
assertThat(data.getValue())
|
assertThat(data.getValue())
|
||||||
|
|
|
@ -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)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -55,7 +55,7 @@ public class CSVFormat implements EventFormat {
|
||||||
event.getData() != null
|
event.getData() != null
|
||||||
? new String(Base64.getEncoder().encode(event.getData().toBytes()), StandardCharsets.UTF_8)
|
? new String(Base64.getEncoder().encode(event.getData().toBytes()), StandardCharsets.UTF_8)
|
||||||
: "null"
|
: "null"
|
||||||
).getBytes();
|
).getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,7 +70,7 @@ public class CSVFormat implements EventFormat {
|
||||||
URI dataschema = splitted[5].equals("null") ? null : URI.create(splitted[5]);
|
URI dataschema = splitted[5].equals("null") ? null : URI.create(splitted[5]);
|
||||||
String subject = splitted[6].equals("null") ? null : splitted[6];
|
String subject = splitted[6].equals("null") ? null : splitted[6];
|
||||||
OffsetDateTime time = splitted[7].equals("null") ? null : Time.parseTime(splitted[7]);
|
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)
|
CloudEventBuilder builder = CloudEventBuilder.fromSpecVersion(sv)
|
||||||
.withId(id)
|
.withId(id)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package io.cloudevents.core.mock;
|
package io.cloudevents.core.mock;
|
||||||
|
|
||||||
import io.cloudevents.CloudEventData;
|
import io.cloudevents.CloudEventData;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
public class MyCloudEventData implements CloudEventData {
|
public class MyCloudEventData implements CloudEventData {
|
||||||
|
@ -14,7 +14,7 @@ public class MyCloudEventData implements CloudEventData {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
return Integer.toString(value).getBytes();
|
return Integer.toString(value).getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getValue() {
|
public int getValue() {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,6 +23,7 @@ import io.cloudevents.types.Time;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
@ -39,9 +40,9 @@ public class Data {
|
||||||
public static final String SUBJECT = "sub";
|
public static final String SUBJECT = "sub";
|
||||||
public static final OffsetDateTime TIME = Time.parseTime("2018-04-26T14:48:09+02:00");
|
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_JSON_SERIALIZED = "{}".getBytes(StandardCharsets.UTF_8);
|
||||||
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes();
|
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes(StandardCharsets.UTF_8);
|
||||||
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes();
|
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 byte[] BINARY_VALUE = { (byte) 0xE0, (byte) 0xFF, (byte) 0x00, (byte) 0x44, (byte) 0xAA }; // Base64: 4P8ARKo=
|
||||||
|
|
||||||
public static final CloudEvent V1_MIN = CloudEventBuilder.v1()
|
public static final CloudEvent V1_MIN = CloudEventBuilder.v1()
|
||||||
|
|
|
@ -158,4 +158,27 @@ public class CloudEventBuilderTest {
|
||||||
).hasMessageContaining("Attribute 'type' cannot be null");
|
).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");
|
).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
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
activesupport (6.0.3.4)
|
activesupport (6.0.6.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 0.7, < 2)
|
i18n (>= 0.7, < 2)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
tzinfo (~> 1.1)
|
tzinfo (~> 1.1)
|
||||||
zeitwerk (~> 2.2, >= 2.2.2)
|
zeitwerk (~> 2.2, >= 2.2.2)
|
||||||
addressable (2.7.0)
|
addressable (2.8.1)
|
||||||
public_suffix (>= 2.0.2, < 5.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
coffee-script (2.4.1)
|
coffee-script (2.4.1)
|
||||||
coffee-script-source
|
coffee-script-source
|
||||||
execjs
|
execjs
|
||||||
|
@ -16,7 +16,7 @@ GEM
|
||||||
colorator (1.1.0)
|
colorator (1.1.0)
|
||||||
commonmarker (0.17.13)
|
commonmarker (0.17.13)
|
||||||
ruby-enum (~> 0.5)
|
ruby-enum (~> 0.5)
|
||||||
concurrent-ruby (1.1.7)
|
concurrent-ruby (1.2.0)
|
||||||
dnsruby (1.61.5)
|
dnsruby (1.61.5)
|
||||||
simpleidn (~> 0.1)
|
simpleidn (~> 0.1)
|
||||||
em-websocket (0.5.2)
|
em-websocket (0.5.2)
|
||||||
|
@ -82,7 +82,7 @@ GEM
|
||||||
octokit (~> 4.0)
|
octokit (~> 4.0)
|
||||||
public_suffix (~> 3.0)
|
public_suffix (~> 3.0)
|
||||||
typhoeus (~> 1.3)
|
typhoeus (~> 1.3)
|
||||||
html-pipeline (2.14.0)
|
html-pipeline (2.14.3)
|
||||||
activesupport (>= 2)
|
activesupport (>= 2)
|
||||||
nokogiri (>= 1.4)
|
nokogiri (>= 1.4)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
|
@ -203,19 +203,19 @@ GEM
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
liquid (4.0.3)
|
liquid (4.0.3)
|
||||||
listen (3.3.0)
|
listen (3.7.1)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
mercenary (0.3.6)
|
mercenary (0.3.6)
|
||||||
mini_portile2 (2.5.0)
|
mini_portile2 (2.8.8)
|
||||||
minima (2.5.1)
|
minima (2.5.1)
|
||||||
jekyll (>= 3.5, < 5.0)
|
jekyll (>= 3.5, < 5.0)
|
||||||
jekyll-feed (~> 0.9)
|
jekyll-feed (~> 0.9)
|
||||||
jekyll-seo-tag (~> 2.1)
|
jekyll-seo-tag (~> 2.1)
|
||||||
minitest (5.14.2)
|
minitest (5.17.0)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
nokogiri (1.11.1)
|
nokogiri (1.18.3)
|
||||||
mini_portile2 (~> 2.5.0)
|
mini_portile2 (~> 2.8.2)
|
||||||
racc (~> 1.4)
|
racc (~> 1.4)
|
||||||
octokit (4.19.0)
|
octokit (4.19.0)
|
||||||
faraday (>= 0.9)
|
faraday (>= 0.9)
|
||||||
|
@ -223,12 +223,12 @@ GEM
|
||||||
pathutil (0.16.2)
|
pathutil (0.16.2)
|
||||||
forwardable-extended (~> 2.6)
|
forwardable-extended (~> 2.6)
|
||||||
public_suffix (3.1.1)
|
public_suffix (3.1.1)
|
||||||
racc (1.5.2)
|
racc (1.8.1)
|
||||||
rake (13.0.1)
|
rake (13.0.1)
|
||||||
rb-fsevent (0.10.4)
|
rb-fsevent (0.10.4)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
rexml (3.2.4)
|
rexml (3.3.9)
|
||||||
rouge (3.23.0)
|
rouge (3.23.0)
|
||||||
ruby-enum (0.8.0)
|
ruby-enum (0.8.0)
|
||||||
i18n
|
i18n
|
||||||
|
@ -250,13 +250,13 @@ GEM
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
typhoeus (1.4.0)
|
typhoeus (1.4.0)
|
||||||
ethon (>= 0.9.0)
|
ethon (>= 0.9.0)
|
||||||
tzinfo (1.2.8)
|
tzinfo (1.2.11)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
unf (0.1.4)
|
unf (0.1.4)
|
||||||
unf_ext
|
unf_ext
|
||||||
unf_ext (0.0.7.7)
|
unf_ext (0.0.7.7)
|
||||||
unicode-display_width (1.7.0)
|
unicode-display_width (1.7.0)
|
||||||
zeitwerk (2.4.1)
|
zeitwerk (2.6.6)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
|
|
|
@ -11,7 +11,7 @@ search_enabled: true
|
||||||
gh_edit_link: true
|
gh_edit_link: true
|
||||||
gh_edit_link_text: "Edit this page on GitHub."
|
gh_edit_link_text: "Edit this page on GitHub."
|
||||||
gh_edit_repository: "https://github.com/cloudevents/sdk-java"
|
gh_edit_repository: "https://github.com/cloudevents/sdk-java"
|
||||||
gh_edit_branch: "master"
|
gh_edit_branch: "main"
|
||||||
gh_edit_source: docs
|
gh_edit_source: docs
|
||||||
gh_edit_view_mode: "tree"
|
gh_edit_view_mode: "tree"
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ binding for CloudEvents:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -43,5 +43,5 @@ public class ProtonAmqpMessageFactory {
|
||||||
The example uses the `vertx-proton` integration to send/receive CloudEvent
|
The example uses the `vertx-proton` integration to send/receive CloudEvent
|
||||||
messages over AMQP:
|
messages over AMQP:
|
||||||
|
|
||||||
- [Vertx AmqpServer](https://github.com/cloudevents/sdk-java/tree/master/examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpServer.java)
|
- [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/master/examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpClient.java)
|
- [Vertx AmqpClient](https://github.com/cloudevents/sdk-java/tree/main/examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpClient.java)
|
||||||
|
|
|
@ -17,7 +17,7 @@ For Maven based projects, use the following dependency:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-api</artifactId>
|
<artifactId>cloudevents-api</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -16,7 +16,7 @@ For Maven based projects, use the following dependency:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-core</artifactId>
|
<artifactId>cloudevents-core</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ final CloudEvent event = CloudEventBuilder.v1()
|
||||||
.withId("000")
|
.withId("000")
|
||||||
.withType("example.demo")
|
.withType("example.demo")
|
||||||
.withSource(URI.create("http://example.com"))
|
.withSource(URI.create("http://example.com"))
|
||||||
.withData("text/plain","Hello world!".getBytes())
|
.withData("text/plain","Hello world!".getBytes("UTF-8"))
|
||||||
.build();
|
.build();
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -87,7 +87,7 @@ with Jackson, add `cloudevents-json-jackson` as a dependency and then using the
|
||||||
`EventFormatProvider`:
|
`EventFormatProvider`:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import io.cloudevents.core.format.EventFormatProvider;
|
import io.cloudevents.core.provider.EventFormatProvider;
|
||||||
import io.cloudevents.jackson.JsonFormat;
|
import io.cloudevents.jackson.JsonFormat;
|
||||||
|
|
||||||
EventFormat format = EventFormatProvider
|
EventFormat format = EventFormatProvider
|
||||||
|
@ -110,6 +110,6 @@ CloudEvent extensions can be materialized in their respective POJOs using the
|
||||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||||
import io.cloudevents.core.provider.ExtensionProvider;
|
import io.cloudevents.core.provider.ExtensionProvider;
|
||||||
|
|
||||||
DistributedTracingExtension dte = ExtensionsParser.getInstance()
|
DistributedTracingExtension dte = ExtensionProvider.getInstance()
|
||||||
.parseExtension(DistributedTracingExtension.class, event);
|
.parseExtension(DistributedTracingExtension.class, event);
|
||||||
```
|
```
|
||||||
|
|
|
@ -27,7 +27,7 @@ HTTP Transport:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-http-basic</artifactId>
|
<artifactId>cloudevents-http-basic</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -48,6 +48,6 @@ public class HttpMessageFactory {
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
- [Standard Java HttpServer](https://github.com/cloudevents/sdk-java/tree/master/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/BasicHttpServer.java)
|
- [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/master/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/HttpURLConnectionClient.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/master/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/JettyServer.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)
|
||||||
|
|
|
@ -3,18 +3,18 @@ title: CloudEvents HTTP Jakarta RESTful Web Services
|
||||||
nav_order: 5
|
nav_order: 5
|
||||||
---
|
---
|
||||||
|
|
||||||
# HTTP Protocol Binding for Jakarta RESTful Web Services
|
# HTTP Protocol Binding for Jakarta EE8 - RESTful Web Services
|
||||||
|
|
||||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||||
|
|
||||||
For Maven based projects, use the following to configure the CloudEvents Jakarta
|
For Maven based projects, use the following to configure the CloudEvents Jakarta
|
||||||
RESTful Web Services Binding:
|
RESTful Web Services Binding for Jakarta EE 8:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-http-restful-ws</artifactId>
|
<artifactId>cloudevents-http-restful-ws</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -118,5 +118,5 @@ public class CloudEventSender {
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
- [Quarkus and Resteasy](https://github.com/cloudevents/sdk-java/tree/master/examples/restful-ws-quarkus)
|
- [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/master/examples/restful-ws-spring-boot)
|
- [Jersey and Spring Boot](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-spring-boot)
|
||||||
|
|
|
@ -14,7 +14,7 @@ HTTP Transport:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-http-vertx</artifactId>
|
<artifactId>cloudevents-http-vertx</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -95,4 +95,4 @@ public class CloudEventClientVerticle extends AbstractVerticle {
|
||||||
|
|
||||||
## Examples:
|
## Examples:
|
||||||
|
|
||||||
- [Vert.x Client and Server](https://github.com/cloudevents/sdk-java/tree/master/examples/vertx)
|
- [Vert.x Client and Server](https://github.com/cloudevents/sdk-java/tree/main/examples/vertx)
|
||||||
|
|
|
@ -42,6 +42,8 @@ Using the Java SDK you can:
|
||||||
| - [Jackson](json-jackson.md) | :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: |
|
| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| - [Proto](protobuf.md) | :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: |
|
| [Kafka Protocol Binding](kafka.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||||
| MQTT Protocol Binding | :x: | :x: |
|
| MQTT Protocol Binding | :x: | :x: |
|
||||||
| NATS Protocol Binding | :x: | :x: |
|
| NATS Protocol Binding | :x: | :x: |
|
||||||
|
@ -62,7 +64,8 @@ receive CloudEvents, check out the dedicated pages:
|
||||||
|
|
||||||
- [AMQP using Proton](amqp-proton.md)
|
- [AMQP using Proton](amqp-proton.md)
|
||||||
- [HTTP using Vert.x](http-vertx.md)
|
- [HTTP using Vert.x](http-vertx.md)
|
||||||
- [HTTP using Jakarta Restful WS](http-jakarta-restful-ws.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 Spring](spring.md)
|
||||||
- [HTTP using Jackson](json-jackson.md)
|
- [HTTP using Jackson](json-jackson.md)
|
||||||
- [Kafka](kafka.md)
|
- [Kafka](kafka.md)
|
||||||
|
@ -72,7 +75,7 @@ and related interfaces, in order to interoperate with the other components of
|
||||||
the SDK, check out the [API module documentation](api.md).
|
the SDK, check out the [API module documentation](api.md).
|
||||||
|
|
||||||
You can also check out the
|
You can also check out the
|
||||||
[**Examples**](https://github.com/cloudevents/sdk-java/tree/master/examples).
|
[**Examples**](https://github.com/cloudevents/sdk-java/tree/main/examples).
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
@ -88,14 +91,20 @@ a different feature from the different sub specs of
|
||||||
[Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format),
|
[Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format),
|
||||||
`MessageReader` /`MessageWriter` to implement
|
`MessageReader` /`MessageWriter` to implement
|
||||||
[Protocol bindings](https://github.com/cloudevents/spec/blob/v1.0/spec.md#protocol-binding)
|
[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
|
- [`cloudevents-json-jackson`] Implementation of [JSON Event format] with
|
||||||
[Jackson](https://github.com/FasterXML/jackson)
|
[Jackson](https://github.com/FasterXML/jackson)
|
||||||
- [`cloudevents-protobuf`] Implementation of [Protobuf Event format] using code generated
|
- [`cloudevents-protobuf`] Implementation of [Protobuf Event format] using code generated
|
||||||
from the standard [protoc](https://github.com/protocolbuffers/protobuf) compiler.
|
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
|
- [`cloudevents-http-vertx`] Implementation of [HTTP Protocol Binding] with
|
||||||
[Vert.x Core](https://vertx.io/)
|
[Vert.x Core](https://vertx.io/)
|
||||||
- [`cloudevents-http-restful-ws`] Implementation of [HTTP Protocol Binding]
|
- [`cloudevents-http-restful-ws`] Implementation of [HTTP Protocol Binding]
|
||||||
for [Jakarta Restful WS](https://jakarta.ee/specifications/restful-ws/)
|
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
|
- [`cloudevents-http-basic`] Generic implementation of [HTTP Protocol
|
||||||
Binding], primarily intended for integrators
|
Binding], primarily intended for integrators
|
||||||
- [`cloudevents-kafka`] Implementation of [Kafka Protocol Binding]
|
- [`cloudevents-kafka`] Implementation of [Kafka Protocol Binding]
|
||||||
|
@ -112,13 +121,17 @@ You can look at the latest published artifacts on
|
||||||
[HTTP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.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
|
[Kafka Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/kafka-protocol-binding.md
|
||||||
[AMQP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/amqp-protocol-binding.md
|
[AMQP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/amqp-protocol-binding.md
|
||||||
[`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/master/api
|
[`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/main/api
|
||||||
[`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/master/core
|
[`cloudevents-bom`]: https://github.com/cloudevents/sdk-java/tree/main/bom
|
||||||
[`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/master/formats/json-jackson
|
[`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/main/core
|
||||||
[`cloudevents-http-vertx`]: https://github.com/cloudevents/sdk-java/tree/master/http/vertx
|
[`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/main/formats/json-jackson
|
||||||
[`cloudevents-http-basic`]: https://github.com/cloudevents/sdk-java/tree/master/http/basic
|
[`cloudevents-protobuf`]: https://github.com/cloudevents/sdk-java/tree/main/formats/protobuf
|
||||||
[`cloudevents-http-restful-ws`]: https://github.com/cloudevents/sdk-java/tree/master/http/restful-ws
|
[`cloudevents-xml`]: https://github.com/cloudevents/sdk-java/tree/main/formats/xml
|
||||||
[`cloudevents-kafka`]: https://github.com/cloudevents/sdk-java/tree/master/kafka
|
[`cloudevents-http-vertx`]: https://github.com/cloudevents/sdk-java/tree/main/http/vertx
|
||||||
[`cloudevents-amqp-proton`]: https://github.com/cloudevents/sdk-java/tree/master/amqp
|
[`cloudevents-http-basic`]: https://github.com/cloudevents/sdk-java/tree/main/http/basic
|
||||||
[`cloudevents-spring`]: https://github.com/cloudevents/sdk-java/tree/master/spring
|
[`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/
|
[http4k]: https://www.http4k.org/guide/modules/cloud_events/
|
||||||
|
|
|
@ -17,7 +17,7 @@ For Maven based projects, use the following dependency:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-json-jackson</artifactId>
|
<artifactId>cloudevents-json-jackson</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -28,9 +28,9 @@ adding the dependency to your project:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import io.cloudevents.CloudEvent;
|
import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.core.format.EventFormatProvider;
|
import io.cloudevents.core.format.ContentType;
|
||||||
|
import io.cloudevents.core.provider.EventFormatProvider;
|
||||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
import io.cloudevents.jackson.JsonFormat;
|
|
||||||
|
|
||||||
CloudEvent event = CloudEventBuilder.v1()
|
CloudEvent event = CloudEventBuilder.v1()
|
||||||
.withId("hello")
|
.withId("hello")
|
||||||
|
@ -40,7 +40,7 @@ CloudEvent event = CloudEventBuilder.v1()
|
||||||
|
|
||||||
byte[]serialized = EventFormatProvider
|
byte[]serialized = EventFormatProvider
|
||||||
.getInstance()
|
.getInstance()
|
||||||
.resolveFormat(JsonFormat.CONTENT_TYPE)
|
.resolveFormat(ContentType.JSON)
|
||||||
.serialize(event);
|
.serialize(event);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,14 @@ nav_order: 5
|
||||||
Implementation of Kafka Protocol Binding to send and receive CloudEvents.
|
Implementation of Kafka Protocol Binding to send and receive CloudEvents.
|
||||||
|
|
||||||
For Maven based projects, use the following to configure the
|
For Maven based projects, use the following to configure the
|
||||||
[Kafka Protocol Binding](https://github.com/cloudevents/spec/blob/master/kafka-protocol-binding.md):
|
[Kafka Protocol Binding](https://github.com/cloudevents/spec/blob/main/kafka-protocol-binding.md):
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-kafka</artifactId>
|
<artifactId>cloudevents-kafka</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ public class CloudEventProducer {
|
||||||
You can configure the Encoding and EventFormat to use to emit the event.
|
You can configure the Encoding and EventFormat to use to emit the event.
|
||||||
|
|
||||||
Check out the
|
Check out the
|
||||||
[`CloudEventSerializer`](https://github.com/cloudevents/sdk-java/tree/master/kafka/src/main/java/io/cloudevents/kafka/CloudEventSerializer.java)
|
[`CloudEventSerializer`](https://github.com/cloudevents/sdk-java/tree/main/kafka/src/main/java/io/cloudevents/kafka/CloudEventSerializer.java)
|
||||||
javadoc for more info.
|
javadoc for more info.
|
||||||
|
|
||||||
### Partition key extension
|
### Partition key extension
|
||||||
|
@ -81,7 +81,7 @@ producerProps.put(
|
||||||
|
|
||||||
When using in your producer, this interceptor will pick the `partitionkey`
|
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.
|
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/master/kafka/src/main/java/io/cloudevents/kafka/PartitionKeyExtensionInterceptor.java)
|
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.
|
javadoc for more info.
|
||||||
|
|
||||||
## Consuming CloudEvents
|
## Consuming CloudEvents
|
||||||
|
|
|
@ -11,26 +11,28 @@ This module provides the Protocol Buffer (protobuf) `EventFormat` implementation
|
||||||
Protobuf runtime and classes generated from the CloudEvents
|
Protobuf runtime and classes generated from the CloudEvents
|
||||||
[proto spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.proto).
|
[proto spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.proto).
|
||||||
|
|
||||||
|
# Setup
|
||||||
For Maven based projects, use the following dependency:
|
For Maven based projects, use the following dependency:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-protobuf</artifactId>
|
<artifactId>cloudevents-protobuf</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
No further configuration is required is use the module.
|
||||||
|
|
||||||
## Using the Protobuf Event Format
|
## Using the Protobuf Event Format
|
||||||
|
|
||||||
You don't need to perform any operation to configure the module, more than
|
### Event serialization
|
||||||
adding the dependency to your project:
|
|
||||||
|
|
||||||
```java
|
```java
|
||||||
import io.cloudevents.CloudEvent;
|
import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.core.format.EventFormatProvider;
|
import io.cloudevents.core.format.ContentType;
|
||||||
|
import io.cloudevents.core.provider.EventFormatProvider;
|
||||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
import io.cloudevents.protobuf.ProtobufFormat;
|
|
||||||
|
|
||||||
CloudEvent event = CloudEventBuilder.v1()
|
CloudEvent event = CloudEventBuilder.v1()
|
||||||
.withId("hello")
|
.withId("hello")
|
||||||
|
@ -40,9 +42,55 @@ CloudEvent event = CloudEventBuilder.v1()
|
||||||
|
|
||||||
byte[]serialized = EventFormatProvider
|
byte[]serialized = EventFormatProvider
|
||||||
.getInstance()
|
.getInstance()
|
||||||
.resolveFormat(ProtobufFormat.CONTENT_TYPE)
|
.resolveFormat(ContentType.PROTO)
|
||||||
.serialize(event);
|
.serialize(event);
|
||||||
```
|
```
|
||||||
|
|
||||||
The `EventFormatProvider` will resolve automatically the `ProtobufFormat` using the
|
The `EventFormatProvider` will automatically resolve the `ProtobufFormat` using the
|
||||||
`ServiceLoader` APIs.
|
`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();
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ For Maven based projects, use the following dependency:
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-spring</artifactId>
|
<artifactId>cloudevents-spring</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.0.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -115,6 +115,18 @@ public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `CodecCustomizer` also works on the client side, so you can use it anywhere that you use a `WebClient` (including in an MVC application). Here's a simple example of a Cloud Event HTTP client:
|
||||||
|
|
||||||
|
```java
|
||||||
|
WebClient client = ...; // Either WebClient.create() or @Autowired a WebClient.Builder
|
||||||
|
CloudEvent event = ...; // Create a CloudEvent
|
||||||
|
Mono<CloudEvent> response = client.post()
|
||||||
|
.uri("http://localhost:8080/events")
|
||||||
|
.bodyValue(event)
|
||||||
|
.retrieve()
|
||||||
|
.bodyToMono(CloudEvent.class);
|
||||||
|
```
|
||||||
|
|
||||||
### Messaging
|
### Messaging
|
||||||
|
|
||||||
Spring Messaging is applicable in a wide range of use cases including WebSockets, JMS, Apache Kafka, RabbitMQ and others. It is also a core part of the Spring Cloud Function and Spring Cloud Stream libraries, so those are natural tools to use to build applications that use Cloud Events. The core abstraction in Spring is the `Message` which carries headers and a payload, just like a `CloudEvent`. Since the mapping is quite direct it makes sense to have a set of converters for Spring applications, so you can consume and produce `CloudEvents`, by treating them as `Messages`. This project provides a converter that you can register in a Spring Messaging application:
|
Spring Messaging is applicable in a wide range of use cases including WebSockets, JMS, Apache Kafka, RabbitMQ and others. It is also a core part of the Spring Cloud Function and Spring Cloud Stream libraries, so those are natural tools to use to build applications that use Cloud Events. The core abstraction in Spring is the `Message` which carries headers and a payload, just like a `CloudEvent`. Since the mapping is quite direct it makes sense to have a set of converters for Spring applications, so you can consume and produce `CloudEvents`, by treating them as `Messages`. This project provides a converter that you can register in a Spring Messaging application:
|
||||||
|
@ -182,12 +194,12 @@ public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
|
||||||
|
|
||||||
Check out the integration tests and samples:
|
Check out the integration tests and samples:
|
||||||
|
|
||||||
- [spring-reactive](https://github.com/cloudevents/sdk-java/tree/master/examples/spring-reactive)
|
- [spring-reactive](https://github.com/cloudevents/sdk-java/tree/main/examples/spring-reactive)
|
||||||
shows how to receive and send CloudEvents through HTTP using Spring Boot and
|
shows how to receive and send CloudEvents through HTTP using Spring Boot and
|
||||||
Webflux.
|
Webflux.
|
||||||
|
|
||||||
- [spring-rsocket](https://github.com/cloudevents/sdk-java/tree/master/examples/spring-rsocket)
|
- [spring-rsocket](https://github.com/cloudevents/sdk-java/tree/main/examples/spring-rsocket)
|
||||||
shows how to receive and send CloudEvents through RSocket using Spring Boot.
|
shows how to receive and send CloudEvents through RSocket using Spring Boot.
|
||||||
|
|
||||||
- [spring-cloud-function](https://github.com/cloudevents/sdk-java/tree/master/examples/spring-function)
|
- [spring-cloud-function](https://github.com/cloudevents/sdk-java/tree/main/examples/spring-function)
|
||||||
shows how to consume and process CloudEvents via Spring Cloud Function.
|
shows how to consume and process CloudEvents via Spring Cloud Function.
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
---
|
||||||
|
title: CloudEvents XML Format
|
||||||
|
nav_order: 4
|
||||||
|
---
|
||||||
|
|
||||||
|
# CloudEvents XML Format
|
||||||
|
|
||||||
|
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-xml)
|
||||||
|
|
||||||
|
This module provides and `EventFormat` implementation that adheres
|
||||||
|
to the CloudEvent XML Format specification.
|
||||||
|
|
||||||
|
This format also supports specialized handling for XML CloudEvent `data`.
|
||||||
|
|
||||||
|
For Maven based projects, use the following dependency:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<artifactId>cloudevents-xml</artifactId>
|
||||||
|
<version>4.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the XML 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.xml")
|
||||||
|
.withSource(URI.create("http://localhost"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
byte[] serialized = EventFormatProvider
|
||||||
|
.getInstance()
|
||||||
|
.resolveFormat(ContentType.XML)
|
||||||
|
.serialize(event);
|
||||||
|
```
|
||||||
|
|
||||||
|
The `EventFormatProvider` will resolve automatically the `XMLFormat` using the
|
||||||
|
`ServiceLoader` APIs.
|
||||||
|
|
||||||
|
XML Document data handling is supported via the `XMLCloudEventData`
|
||||||
|
facility. This convenience wrapper can be used with `any` other supported
|
||||||
|
format.
|
||||||
|
|
||||||
|
```java
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import io.cloudevents.xml.XMLCloudEventData;
|
||||||
|
|
||||||
|
// Create the business event data.
|
||||||
|
Document xmlDoc = .... ;
|
||||||
|
|
||||||
|
// Wrap it into CloudEventData
|
||||||
|
CloudEventData myData = XMLCloudEventData.wrap(xmlDoc);
|
||||||
|
|
||||||
|
// Construct the event
|
||||||
|
CloudEvent event = CloudEventBuilder.v1()
|
||||||
|
.withId("hello")
|
||||||
|
.withType("example.xml")
|
||||||
|
.withSource(URI.create("http://localhost"))
|
||||||
|
.withData(myData)
|
||||||
|
.build();
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,15 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-amqp-proton-example</artifactId>
|
<artifactId>cloudevents-amqp-proton-example</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<vertx.version>4.0.0.Beta1</vertx.version>
|
<module-name>cloudevents.example.amqp.proton</module-name>
|
||||||
|
<vertx.version>4.0.0.Beta1</vertx.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -15,6 +15,7 @@ import org.apache.qpid.proton.message.Message;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A example vertx-based AMQP client that interacts with a remote AMQP server to send and receive CloudEvent messages.
|
* A example vertx-based AMQP client that interacts with a remote AMQP server to send and receive CloudEvent messages.
|
||||||
|
@ -71,7 +72,7 @@ public class AmqpClient {
|
||||||
.withSource(URI.create("http://127.0.0.1/amqp-client"))
|
.withSource(URI.create("http://127.0.0.1/amqp-client"))
|
||||||
.withType("com.example.sampletype1")
|
.withType("com.example.sampletype1")
|
||||||
.withTime(Time.parseTime("2020-11-06T21:47:12.037467+00:00"))
|
.withTime(Time.parseTime("2020-11-06T21:47:12.037467+00:00"))
|
||||||
.withData(payload.toString().getBytes())
|
.withData(payload.toString().getBytes(StandardCharsets.UTF_8))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final Message message = ProtonAmqpMessageFactory.createWriter().writeBinary(event);
|
final Message message = ProtonAmqpMessageFactory.createWriter().writeBinary(event);
|
||||||
|
|
|
@ -2,6 +2,7 @@ package io.cloudevents.examples.amqp.vertx;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.time.OffsetDateTime;
|
import java.time.OffsetDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -81,7 +82,7 @@ public class AmqpServer {
|
||||||
.withType("com.example.sampletype1")
|
.withType("com.example.sampletype1")
|
||||||
.withSource(URI.create("http://127.0.0.1/amqp-server"))
|
.withSource(URI.create("http://127.0.0.1/amqp-server"))
|
||||||
.withTime(OffsetDateTime.now())
|
.withTime(OffsetDateTime.now())
|
||||||
.withData("{\"temp\": 5}".getBytes())
|
.withData("{\"temp\": 5}".getBytes(StandardCharsets.UTF_8))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
final Message message = writer.writeBinary(event);
|
final Message message = writer.writeBinary(event);
|
||||||
|
|
|
@ -21,11 +21,14 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-basic-http-example</artifactId>
|
<artifactId>cloudevents-basic-http-example</artifactId>
|
||||||
|
<properties>
|
||||||
|
<module-name>cloudevents.example.basic.http</module-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -41,7 +44,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.eclipse.jetty</groupId>
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
<artifactId>jetty-server</artifactId>
|
<artifactId>jetty-server</artifactId>
|
||||||
<version>9.4.38.v20210224</version>
|
<version>9.4.55.v20240627</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-kafka-example</artifactId>
|
<artifactId>cloudevents-kafka-example</artifactId>
|
||||||
|
<properties>
|
||||||
|
<module-name>cloudevents.example.kafka</module-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.apache.kafka.clients.producer.RecordMetadata;
|
||||||
import org.apache.kafka.common.serialization.StringSerializer;
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ -33,7 +34,7 @@ public class SampleProducer {
|
||||||
|
|
||||||
// Configure the CloudEventSerializer to emit events as json structured events
|
// Configure the CloudEventSerializer to emit events as json structured events
|
||||||
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CloudEventSerializer.class);
|
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CloudEventSerializer.class);
|
||||||
props.put(CloudEventSerializer.ENCODING_CONFIG, Encoding.BINARY);
|
props.put(CloudEventSerializer.ENCODING_CONFIG, Encoding.STRUCTURED);
|
||||||
props.put(CloudEventSerializer.EVENT_FORMAT_CONFIG, JsonFormat.CONTENT_TYPE);
|
props.put(CloudEventSerializer.EVENT_FORMAT_CONFIG, JsonFormat.CONTENT_TYPE);
|
||||||
|
|
||||||
// Create the KafkaProducer
|
// Create the KafkaProducer
|
||||||
|
@ -42,7 +43,7 @@ public class SampleProducer {
|
||||||
|
|
||||||
// Create an event template to set basic CloudEvent attributes
|
// Create an event template to set basic CloudEvent attributes
|
||||||
CloudEventBuilder eventTemplate = CloudEventBuilder.v1()
|
CloudEventBuilder eventTemplate = CloudEventBuilder.v1()
|
||||||
.withSource(URI.create("https://github.com/cloudevents/sdk-java/tree/master/examples/kafka"))
|
.withSource(URI.create("https://github.com/cloudevents/sdk-java/tree/main/examples/kafka"))
|
||||||
.withType("producer.example");
|
.withType("producer.example");
|
||||||
|
|
||||||
for (int i = 0; i < MESSAGE_COUNT; i++) {
|
for (int i = 0; i < MESSAGE_COUNT; i++) {
|
||||||
|
@ -53,7 +54,7 @@ public class SampleProducer {
|
||||||
// Create the event starting from the template
|
// Create the event starting from the template
|
||||||
CloudEvent event = eventTemplate.newBuilder()
|
CloudEvent event = eventTemplate.newBuilder()
|
||||||
.withId(id)
|
.withId(id)
|
||||||
.withData("text/plain", data.getBytes())
|
.withData("text/plain", data.getBytes(StandardCharsets.UTF_8))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Send the record
|
// Send the record
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-parent</artifactId>
|
<artifactId>cloudevents-parent</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
<modules>
|
<modules>
|
||||||
<module>kafka</module>
|
<module>kafka</module>
|
||||||
<module>restful-ws-quarkus</module>
|
<module>restful-ws-quarkus</module>
|
||||||
|
<module>restful-ws-microprofile-liberty</module>
|
||||||
<module>vertx</module>
|
<module>vertx</module>
|
||||||
<module>basic-http</module>
|
<module>basic-http</module>
|
||||||
<module>restful-ws-spring-boot</module>
|
<module>restful-ws-spring-boot</module>
|
||||||
|
@ -28,6 +29,7 @@
|
||||||
<module>spring-reactive</module>
|
<module>spring-reactive</module>
|
||||||
<module>spring-rsocket</module>
|
<module>spring-rsocket</module>
|
||||||
<module>spring-function</module>
|
<module>spring-function</module>
|
||||||
|
<module>rocketmq</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Cloudevents Restful WS Microprofile Example
|
||||||
|
|
||||||
|
This project uses Microprofile 5.0 with OpenLiberty
|
||||||
|
|
||||||
|
If you would like to know more about Microprofile go to https://microprofile.io
|
||||||
|
|
||||||
|
This Example uses Jakarta EE9 features as such the top level namespace of the `ws-api` packages has changed from `javax` to `jakarta` and uses the `cloudevents-http-restful-ws-jakarta` artifact.
|
||||||
|
|
||||||
|
## Build and Execution
|
||||||
|
|
||||||
|
### Running the application in dev mode
|
||||||
|
|
||||||
|
You can run your application in dev mode that enables live coding using:
|
||||||
|
```
|
||||||
|
mvn liberty:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Packaging and running the application
|
||||||
|
|
||||||
|
To Package and run as a minimum jar without a full OpenLiberty server
|
||||||
|
```
|
||||||
|
mvn liberty:package -Drunnable=mvn liberty:package -Dinclude=runnable
|
||||||
|
```
|
||||||
|
|
||||||
|
### Making requests against the server
|
||||||
|
|
||||||
|
This sample application has a `/events` RESTful endpoint on the application `cloudevents-restful-ws-microprofile-example
|
||||||
|
the base application is available at `http://localhost:9080/cloudevents-restful-ws-microprofile-example/`
|
||||||
|
|
||||||
|
There are three operations that can be performed:
|
||||||
|
#### GET /events
|
||||||
|
Returns a Cloud event with a payload containing a message
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -v http://localhost:9080/cloudevents-restful-ws-microprofile-example/events
|
||||||
|
|
||||||
|
* Trying 127.0.0.1:9080...
|
||||||
|
* Connected to localhost (127.0.0.1) port 9080 (#0)
|
||||||
|
> GET /cloudevents-restful-ws-microprofile-example/events HTTP/1.1
|
||||||
|
> Host: localhost:9080
|
||||||
|
> User-Agent: curl/7.83.1
|
||||||
|
> Accept: */*
|
||||||
|
>
|
||||||
|
* Mark bundle as not supporting multiuse
|
||||||
|
< HTTP/1.1 201 no-content
|
||||||
|
< Ce-Id: hello
|
||||||
|
< Ce-Source: http://localhost
|
||||||
|
< Ce-Specversion: 1.0
|
||||||
|
< Ce-Type: example.http
|
||||||
|
< Content-Type: application/json
|
||||||
|
< Content-Language: en-GB
|
||||||
|
< Content-Length: 64
|
||||||
|
< Date: Wed, 17 Aug 2022 14:01:50 GMT
|
||||||
|
<
|
||||||
|
{"message":"Welcome to this Cloudevents + Microprofile example"}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /events
|
||||||
|
POST a Cloudevent with a payload that is printed out in the server logs
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -v http://localhost:9080/cloudevents-restful-ws-microprofile-example/events \
|
||||||
|
-H "Ce-Specversion: 1.0" \
|
||||||
|
-H "Ce-Type: User" \
|
||||||
|
-H "Ce-Source: io.cloudevents.examples/user" \
|
||||||
|
-H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Ce-Subject: SUBJ-0001" \
|
||||||
|
-d "hello"
|
||||||
|
|
||||||
|
* Trying 127.0.0.1:9080...
|
||||||
|
* Connected to localhost (127.0.0.1) port 9080 (#0)
|
||||||
|
> POST /cloudevents-restful-ws-microprofile-example/events HTTP/1.1
|
||||||
|
> Host: localhost:9080
|
||||||
|
> User-Agent: curl/7.83.1
|
||||||
|
> Accept: */*
|
||||||
|
> Ce-Specversion: 1.0
|
||||||
|
> Ce-Type: User
|
||||||
|
> Ce-Source: io.cloudevents.examples/user
|
||||||
|
> Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78
|
||||||
|
> Content-Type: application/json
|
||||||
|
> Ce-Subject: SUBJ-0001
|
||||||
|
> Content-Length: 5
|
||||||
|
>
|
||||||
|
* Mark bundle as not supporting multiuse
|
||||||
|
< HTTP/1.1 204 No Content
|
||||||
|
< Content-Language: en-GB
|
||||||
|
< Content-Length: 0
|
||||||
|
< Date: Thu, 18 Aug 2022 13:33:03 GMT
|
||||||
|
<
|
||||||
|
* Connection #0 to host localhost left intact
|
||||||
|
```
|
||||||
|
Server log statement
|
||||||
|
```
|
||||||
|
[INFO] Received request providing a event with body hello
|
||||||
|
[INFO] CloudEvent{id='536808d3-88be-4077-9d7a-a3f162705f78', source=io.cloudevents.examples/user, type='User', datacontenttype='application/json', subject='SUBJ-0001', data=BytesCloudEventData{value=[104, 101, 108, 108, 111]}, extensions={}}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### POST /events/echo
|
||||||
|
POST a Cloudevent with a payload and have it echoed back as a Cloudevent
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl -v http://localhost:9080/cloudevents-restful-ws-microprofile-example/events/echo \
|
||||||
|
-H "Ce-Specversion: 1.0" \
|
||||||
|
-H "Ce-Type: User" \
|
||||||
|
-H "Ce-Source: io.cloudevents.examples/user" \
|
||||||
|
-H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Ce-Subject: SUBJ-0001" \
|
||||||
|
-d "hello"
|
||||||
|
|
||||||
|
* Trying 127.0.0.1:9080...
|
||||||
|
* Connected to localhost (127.0.0.1) port 9080 (#0)
|
||||||
|
> POST /cloudevents-restful-ws-microprofile-example/rest/events/echo HTTP/1.1
|
||||||
|
> Host: localhost:9080
|
||||||
|
> User-Agent: curl/7.83.1
|
||||||
|
> Accept: */*
|
||||||
|
> Ce-Specversion: 1.0
|
||||||
|
> Ce-Type: User
|
||||||
|
> Ce-Source: io.cloudevents.examples/user
|
||||||
|
> Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78
|
||||||
|
> Content-Type: application/json
|
||||||
|
> Ce-Subject: SUBJ-0001
|
||||||
|
> Content-Length: 5
|
||||||
|
>
|
||||||
|
* Mark bundle as not supporting multiuse
|
||||||
|
< HTTP/1.1 200 OK
|
||||||
|
< Ce-Id: echo
|
||||||
|
< Ce-Source: http://localhost
|
||||||
|
< Ce-Specversion: 1.0
|
||||||
|
< Ce-Type: echo.http
|
||||||
|
< Content-Type: application/json
|
||||||
|
< Content-Language: en-GB
|
||||||
|
< Content-Length: 17
|
||||||
|
< Date: Wed, 17 Aug 2022 12:57:59 GMT
|
||||||
|
<
|
||||||
|
{"echo": "hello"}* Connection #0 to host localhost left intact
|
||||||
|
```
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,185 @@
|
||||||
|
<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">
|
||||||
|
<parent>
|
||||||
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
|
<relativePath>../</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<artifactId>cloudevents-restful-ws-microprofile-liberty-example</artifactId>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<openliberty.maven.version>3.5.1</openliberty.maven.version>
|
||||||
|
<app.name>cloudevents-microprofile</app.name>
|
||||||
|
<module-name>cloudevents.example.restful.ws.microprofile.liberty</module-name>
|
||||||
|
|
||||||
|
<!-- Liberty server properties -->
|
||||||
|
<final.name>cloudeventsServer</final.name>
|
||||||
|
<testServerHttpPort>9080</testServerHttpPort>
|
||||||
|
<testServerHttpsPort>9443</testServerHttpsPort>
|
||||||
|
<!--This is set in the ibm-web-ext.xml file -->
|
||||||
|
<warContext>cloudeventsexperiments</warContext>
|
||||||
|
<package.file>${project.build.directory}/${app.name}.zip</package.file>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>runnable</id>
|
||||||
|
<properties>
|
||||||
|
<package.file>${project.build.directory}/${app.name}.jar</package.file>
|
||||||
|
<packaging.type>runnable</packaging.type>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.microprofile</groupId>
|
||||||
|
<artifactId>microprofile</artifactId>
|
||||||
|
<version>5.0</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<artifactId>cloudevents-http-restful-ws-jakarta</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-core</artifactId>
|
||||||
|
<version>3.6.11</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<version>3.3.2</version>
|
||||||
|
<configuration>
|
||||||
|
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||||
|
<packagingExcludes>pom.xml</packagingExcludes>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-dependency-plugin</artifactId>
|
||||||
|
<version>2.10</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-server-files</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-dependencies</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includeArtifactIds>server-snippet</includeArtifactIds>
|
||||||
|
<prependGroupId>true</prependGroupId>
|
||||||
|
<outputDirectory>
|
||||||
|
${project.build.directory}/wlp/usr/servers/${wlpServerName}/configDropins/defaults
|
||||||
|
</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-resources-plugin</artifactId>
|
||||||
|
<version>2.7</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>copy-app</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>copy-resources</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>${project.build.directory}/wlp/usr/servers/${wlpServerName}/dropins
|
||||||
|
</outputDirectory>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}</directory>
|
||||||
|
<includes>
|
||||||
|
<include>${project.artifactId}-${project.version}.war</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<!-- Enable liberty-maven plugin -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.openliberty.tools</groupId>
|
||||||
|
<artifactId>liberty-maven-plugin</artifactId>
|
||||||
|
<version>${openliberty.maven.version}</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>package-server</id>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>create</goal>
|
||||||
|
<goal>install-feature</goal>
|
||||||
|
<goal>deploy</goal>
|
||||||
|
<goal>package</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>target/wlp-package</outputDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<include>runnable</include>
|
||||||
|
<serverName>${final.name}</serverName>
|
||||||
|
<bootstrapProperties>
|
||||||
|
<project.name>${final.name}</project.name>
|
||||||
|
<jwt.issuer>https://server.example.com</jwt.issuer>
|
||||||
|
<app.context.root>/</app.context.root>
|
||||||
|
</bootstrapProperties>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- Plugin to run functional tests -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-failsafe-plugin</artifactId>
|
||||||
|
<version>2.18.1</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>integration-test</phase>
|
||||||
|
<id>integration-test</id>
|
||||||
|
<goals>
|
||||||
|
<goal>integration-test</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<includes>
|
||||||
|
<include>**/it/**</include>
|
||||||
|
</includes>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<liberty.test.port>${testServerHttpPort}</liberty.test.port>
|
||||||
|
<war.name>${warContext}</war.name>
|
||||||
|
<running.bluemix>${running.bluemix}</running.bluemix>
|
||||||
|
<cf.context.root>${cf.context.root}</cf.context.root>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>verify-results</id>
|
||||||
|
<goals>
|
||||||
|
<goal>verify</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<summaryFile>${project.build.directory}/test-reports/it/failsafe-summary.xml</summaryFile>
|
||||||
|
<reportsDirectory>${project.build.directory}/test-reports/it</reportsDirectory>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
|
@ -0,0 +1,9 @@
|
||||||
|
package io.cloudevents.examples.microprofile;
|
||||||
|
|
||||||
|
import jakarta.ws.rs.ApplicationPath;
|
||||||
|
import jakarta.ws.rs.core.Application;
|
||||||
|
|
||||||
|
@ApplicationPath("/")
|
||||||
|
public class CloudEventsApplication extends Application {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,56 @@
|
||||||
|
package io.cloudevents.examples.microprofile;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import jakarta.enterprise.context.RequestScoped;
|
||||||
|
import jakarta.ws.rs.*;
|
||||||
|
import jakarta.ws.rs.core.MediaType;
|
||||||
|
import jakarta.ws.rs.core.Response;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
@RequestScoped
|
||||||
|
@Path("events")
|
||||||
|
public class CloudEventsJaxrsService {
|
||||||
|
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
|
public CloudEvent retrieveEvent(){
|
||||||
|
System.out.println("Received request for an event");
|
||||||
|
return CloudEventBuilder.v1()
|
||||||
|
.withData("{\"message\":\"Welcome to this Cloudevents + Microprofile example\"}".getBytes(StandardCharsets.UTF_8))
|
||||||
|
.withDataContentType("application/json")
|
||||||
|
.withId("hello")
|
||||||
|
.withType("example.http")
|
||||||
|
.withSource(URI.create("http://localhost"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Example Curl - curl -v http://localhost:9080/cloudevents-restful-ws-microprofile-example/events -H "Ce-Specversion: 1.0" -H "Ce-Type: User" -H "Ce-Source: io.cloudevents.examples/user" -H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78" -H "Content-Type: application/json" -H "Ce-Subject: SUBJ-0001" -d "hello"
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public Response postEvent(CloudEvent event){
|
||||||
|
System.out.println("Received request providing a event with body "+ new String(event.getData().toBytes(), StandardCharsets.UTF_8));
|
||||||
|
System.out.println(event);
|
||||||
|
return Response.noContent().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
//Example Curl - curl -v http://localhost:9080/cloudevents-restful-ws-microprofile-example/events/echo -H "Ce-Specversion: 1.0" -H "Ce-Type: User" -H "Ce-Source: io.cloudevents.examples/user" -H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78" -H "Content-Type: application/json" -H "Ce-Subject: SUBJ-0001" -d "hello"
|
||||||
|
|
||||||
|
@POST
|
||||||
|
@Path("/echo")
|
||||||
|
@Consumes(MediaType.APPLICATION_JSON)
|
||||||
|
public CloudEvent echo(CloudEvent event){
|
||||||
|
return CloudEventBuilder.v1()
|
||||||
|
.withData("application/json", ("{\"echo\": \"" + new String(event.getData().toBytes(),StandardCharsets.UTF_8) + "\"}").getBytes(StandardCharsets.UTF_8))
|
||||||
|
.withId("echo")
|
||||||
|
.withType("echo.http")
|
||||||
|
.withSource(URI.create("http://localhost"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<server description="${project.name}">
|
||||||
|
|
||||||
|
<featureManager>
|
||||||
|
<feature>microProfile-5.0</feature>
|
||||||
|
</featureManager>
|
||||||
|
|
||||||
|
|
||||||
|
<httpEndpoint id="defaultHttpEndpoint"
|
||||||
|
httpPort="9080"
|
||||||
|
httpsPort="9443"/>
|
||||||
|
|
||||||
|
<webApplication location="${project.name}.war">
|
||||||
|
<classloader apiTypeVisibility="+third-party" />
|
||||||
|
</webApplication>
|
||||||
|
<mpMetrics authentication="false"/>
|
||||||
|
|
||||||
|
<!-- This is the keystore that will be used by SSL and by JWT. -->
|
||||||
|
<keyStore id="defaultKeyStore" location="public.jks" type="JKS" password="atbash" />
|
||||||
|
|
||||||
|
|
||||||
|
<!-- The MP JWT configuration that injects the caller's JWT into a ResourceScoped bean for inspection. -->
|
||||||
|
<mpJwt id="jwtUserConsumer" keyName="theKeyId" audiences="targetService" issuer="${jwt.issuer}"/>
|
||||||
|
|
||||||
|
</server>
|
|
@ -1,7 +1,11 @@
|
||||||
# Cloudevents Restful WS Quarkus example
|
# Cloudevents Restful WS Quarkus example
|
||||||
|
|
||||||
This sample application has a `/users` REST endpoint in which you can manage the different users.
|
This sample application has a `/users` REST endpoint in which you can manage the different users.
|
||||||
The way to create users is through CloudEvents. Here is an example POST:
|
The way to create users is through CloudEvents.
|
||||||
|
|
||||||
|
## Example requests
|
||||||
|
|
||||||
|
### [Binary Content mode](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md#31-binary-content-mode)
|
||||||
|
|
||||||
```shell script
|
```shell script
|
||||||
curl -v http://localhost:8080/users \
|
curl -v http://localhost:8080/users \
|
||||||
|
@ -30,6 +34,29 @@ curl -v http://localhost:8080/users \
|
||||||
< Location: http://localhost:8080/users
|
< Location: http://localhost:8080/users
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### [Structured Content mode](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/bindings/http-protocol-binding.md#32-structured-content-mode)
|
||||||
|
|
||||||
|
```shell script
|
||||||
|
curl -v http://localhost:8080/users \
|
||||||
|
-H "Content-Type: application/cloudevents+json" \
|
||||||
|
-d @examples/user_structured.json
|
||||||
|
|
||||||
|
> POST /users HTTP/1.1
|
||||||
|
> Host: localhost:8080
|
||||||
|
> User-Agent: curl/7.82.0
|
||||||
|
> Accept: */*
|
||||||
|
> Content-Type: application/cloudevents+json
|
||||||
|
> Content-Length: 290
|
||||||
|
>
|
||||||
|
|
||||||
|
< HTTP/1.1 201 Created
|
||||||
|
< Location: http://localhost:8081/users
|
||||||
|
< content-length: 0
|
||||||
|
<
|
||||||
|
```
|
||||||
|
|
||||||
|
### Generated events
|
||||||
|
|
||||||
In order to also show how to create and send CloudEvents, generated events will be periodically sent
|
In order to also show how to create and send CloudEvents, generated events will be periodically sent
|
||||||
each 2 seconds through HTTP to the same endpoint using a REST client.
|
each 2 seconds through HTTP to the same endpoint using a REST client.
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"specversion" : "1.0",
|
||||||
|
"type" : "User",
|
||||||
|
"source": "io.cloudevents.examples/user",
|
||||||
|
"id": "536808d3-88be-4077-9d7a-a3f162705f78",
|
||||||
|
"subject": "SUBJ-0001",
|
||||||
|
"data" : {
|
||||||
|
"username": "jsmith",
|
||||||
|
"firstName": "John",
|
||||||
|
"lastName": "Smith",
|
||||||
|
"age": 37
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,11 +5,12 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>cloudevents-restful-ws-quarkus-example</artifactId>
|
<artifactId>cloudevents-restful-ws-quarkus-example</artifactId>
|
||||||
<properties>
|
<properties>
|
||||||
|
<module-name>cloudevents.example.restful.ws.quarkus</module-name>
|
||||||
<quarkus-plugin.version>1.10.3.Final</quarkus-plugin.version>
|
<quarkus-plugin.version>1.10.3.Final</quarkus-plugin.version>
|
||||||
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
|
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
|
||||||
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
|
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
package io.cloudevents.examples.quarkus.client;
|
package io.cloudevents.examples.quarkus.client;
|
||||||
|
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.Produces;
|
|
||||||
import javax.ws.rs.core.MediaType;
|
|
||||||
|
|
||||||
import io.cloudevents.CloudEvent;
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.jackson.JsonFormat;
|
||||||
|
|
||||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||||
|
|
||||||
@Path("/users")
|
@Path("/users")
|
||||||
@RegisterRestClient
|
@RegisterRestClient
|
||||||
public interface UserClient {
|
public interface UserClient {
|
||||||
|
|
||||||
|
// This will emit binary encoded events.
|
||||||
|
// To use structured JSON encoding use @Consumes(JsonFormat.CONTENT_TYPE).
|
||||||
@POST
|
@POST
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
void emitBinary(CloudEvent event);
|
||||||
void emit(CloudEvent event);
|
|
||||||
|
@POST
|
||||||
|
@Consumes(JsonFormat.CONTENT_TYPE)
|
||||||
|
void emitStructured(CloudEvent event);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
import io.cloudevents.core.data.PojoCloudEventData;
|
import io.cloudevents.core.data.PojoCloudEventData;
|
||||||
import io.cloudevents.examples.quarkus.model.User;
|
import io.cloudevents.examples.quarkus.model.User;
|
||||||
import io.quarkus.scheduler.Scheduled;
|
import io.quarkus.scheduler.Scheduled;
|
||||||
import io.smallrye.mutiny.Uni;
|
|
||||||
|
|
||||||
@ApplicationScoped
|
@ApplicationScoped
|
||||||
public class UserEventsGenerator {
|
public class UserEventsGenerator {
|
||||||
|
@ -38,8 +37,13 @@ public class UserEventsGenerator {
|
||||||
@Scheduled(every="2s")
|
@Scheduled(every="2s")
|
||||||
public void init() {
|
public void init() {
|
||||||
CloudEvent event = createEvent(userCount++);
|
CloudEvent event = createEvent(userCount++);
|
||||||
LOGGER.info("try to emit user: {}", event.getId());
|
if(userCount % 2 == 0) {
|
||||||
userClient.emit(event);
|
LOGGER.info("try to emit binary event for user: {}", event.getId());
|
||||||
|
userClient.emitBinary(event);
|
||||||
|
} else {
|
||||||
|
LOGGER.info("try to emit structured event for user: {}", event.getId());
|
||||||
|
userClient.emitStructured(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CloudEvent createEvent(long id) {
|
private CloudEvent createEvent(long id) {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package io.cloudevents.examples.quarkus.resources;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import io.cloudevents.CloudEvent;
|
import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.examples.quarkus.model.User;
|
import io.cloudevents.examples.quarkus.model.User;
|
||||||
|
import io.cloudevents.jackson.JsonFormat;
|
||||||
import io.cloudevents.jackson.PojoCloudEventDataMapper;
|
import io.cloudevents.jackson.PojoCloudEventDataMapper;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -17,7 +18,7 @@ import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@Path("/users")
|
@Path("/users")
|
||||||
@Consumes(MediaType.APPLICATION_JSON)
|
@Consumes({MediaType.APPLICATION_JSON, JsonFormat.CONTENT_TYPE})
|
||||||
@Produces(MediaType.APPLICATION_JSON)
|
@Produces(MediaType.APPLICATION_JSON)
|
||||||
public class UserResource {
|
public class UserResource {
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.quarkus.test.junit.QuarkusTest;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import static io.restassured.RestAssured.given;
|
import static io.restassured.RestAssured.given;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
|
||||||
|
|
||||||
@QuarkusTest
|
@QuarkusTest
|
||||||
public class UserResourceTest {
|
public class UserResourceTest {
|
||||||
|
|
|
@ -11,11 +11,11 @@ mvn spring-boot:run
|
||||||
You can try sending a request using `curl`:
|
You can try sending a request using `curl`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
curl -X POST -v -d '{"username": "slinkydeveloper", "firstName": "Francesco", "lastName": "Guardiani", "age": 23}' \ ~
|
curl -X POST -v -d '{"username": "slinkydeveloper", "firstName": "Francesco", "lastName": "Guardiani", "age": 23}' \
|
||||||
-H'Content-type: application/json' \
|
-H 'Content-type: application/json' \
|
||||||
-H'Ce-id: 1' \
|
-H 'Ce-id: 1' \
|
||||||
-H'Ce-source: cloud-event-example' \
|
-H 'Ce-source: cloud-event-example' \
|
||||||
-H'Ce-type: happybirthday.myapplication' \
|
-H 'Ce-type: happybirthday.myapplication' \
|
||||||
-H'Ce-specversion: 1.0' \
|
-H 'Ce-specversion: 1.0' \
|
||||||
http://localhost:8080/happy_birthday
|
http://localhost:8080/happy_birthday
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,15 +5,16 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-spring-boot-example</artifactId>
|
<artifactId>cloudevents-spring-boot-example</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<module-name>cloudevents.example.spring.boot</module-name>
|
||||||
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
|
<spring-boot.version>2.3.2.RELEASE</spring-boot.version>
|
||||||
<spring.version>5.2.8.RELEASE</spring.version>
|
<spring.version>5.2.9.RELEASE</spring.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# RocketMQ + CloudEvents Sample
|
||||||
|
|
||||||
|
This example demonstrates the integration of [RocketMQ 5.x client library](https://github.com/apache/rocketmq-clients)
|
||||||
|
with CloudEvents to create a RocketMQ binding.
|
||||||
|
|
||||||
|
## Building the Project
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mvn package
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setting Up a RocketMQ Instance
|
||||||
|
|
||||||
|
Follow the [quickstart guide](https://rocketmq.apache.org/docs/quick-start/01quickstart) on the official RocketMQ
|
||||||
|
website to set up the necessary components, including nameserver, proxy, and broker.
|
||||||
|
|
||||||
|
## Event Production
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mvn exec:java -Dexec.mainClass="io.cloudevents.examples.rocketmq.RocketmqProducer" -Dexec.args="foobar:8081 sample-topic"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Consumption
|
||||||
|
|
||||||
|
```shell
|
||||||
|
mvn exec:java -Dexec.mainClass="io.cloudevents.examples.rocketmq.RocketmqConsumer" -Dexec.args="foobar:8081 sample-topic sample-consumer-group"
|
||||||
|
```
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<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/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>cloudevents-rocketmq-example</artifactId>
|
||||||
|
<properties>
|
||||||
|
<module-name>cloudevents.example.rocketmq</module-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<artifactId>cloudevents-rocketmq</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.cloudevents.examples.rocketmq;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.message.MessageReader;
|
||||||
|
import io.cloudevents.rocketmq.RocketMqMessageFactory;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import org.apache.rocketmq.client.apis.ClientConfiguration;
|
||||||
|
import org.apache.rocketmq.client.apis.ClientException;
|
||||||
|
import org.apache.rocketmq.client.apis.ClientServiceProvider;
|
||||||
|
import org.apache.rocketmq.client.apis.consumer.ConsumeResult;
|
||||||
|
import org.apache.rocketmq.client.apis.consumer.FilterExpression;
|
||||||
|
import org.apache.rocketmq.client.apis.consumer.PushConsumer;
|
||||||
|
|
||||||
|
public class RocketmqConsumer {
|
||||||
|
private RocketmqConsumer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws InterruptedException, ClientException, IOException {
|
||||||
|
if (args.length < 3) {
|
||||||
|
System.out.println("Usage: rocketmq_consumer <endpoints> <topic> <consumer_group>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ClientServiceProvider provider = ClientServiceProvider.loadService();
|
||||||
|
String endpoints = args[0];
|
||||||
|
ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder()
|
||||||
|
.setEndpoints(endpoints)
|
||||||
|
.build();
|
||||||
|
FilterExpression filterExpression = new FilterExpression();
|
||||||
|
String topic = args[1];
|
||||||
|
String consumerGroup = args[2];
|
||||||
|
|
||||||
|
// Create the RocketMQ Consumer.
|
||||||
|
PushConsumer pushConsumer = provider.newPushConsumerBuilder()
|
||||||
|
.setClientConfiguration(clientConfiguration)
|
||||||
|
.setConsumerGroup(consumerGroup)
|
||||||
|
.setSubscriptionExpressions(Collections.singletonMap(topic, filterExpression))
|
||||||
|
.setMessageListener(messageView -> {
|
||||||
|
final MessageReader reader = RocketMqMessageFactory.createReader(messageView);
|
||||||
|
final CloudEvent event = reader.toEvent();
|
||||||
|
System.out.println("Received event=" + event + ", messageId=" + messageView.getMessageId());
|
||||||
|
return ConsumeResult.SUCCESS;
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
// Block the main thread, no need for production environment.
|
||||||
|
Thread.sleep(Long.MAX_VALUE);
|
||||||
|
// Close the push consumer when you don't need it anymore.
|
||||||
|
pushConsumer.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
package io.cloudevents.examples.rocketmq;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.v1.CloudEventBuilder;
|
||||||
|
import io.cloudevents.rocketmq.RocketMqMessageFactory;
|
||||||
|
import io.cloudevents.types.Time;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.apache.rocketmq.client.apis.ClientConfiguration;
|
||||||
|
import org.apache.rocketmq.client.apis.ClientException;
|
||||||
|
import org.apache.rocketmq.client.apis.ClientServiceProvider;
|
||||||
|
import org.apache.rocketmq.client.apis.message.Message;
|
||||||
|
import org.apache.rocketmq.client.apis.producer.Producer;
|
||||||
|
import org.apache.rocketmq.client.apis.producer.SendReceipt;
|
||||||
|
import org.apache.rocketmq.shaded.com.google.gson.Gson;
|
||||||
|
|
||||||
|
public class RocketmqProducer {
|
||||||
|
private RocketmqProducer() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws ClientException, IOException {
|
||||||
|
if (args.length < 2) {
|
||||||
|
System.out.println("Usage: rocketmq_producer <endpoints> <topic>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final ClientServiceProvider provider = ClientServiceProvider.loadService();
|
||||||
|
String endpoints = args[0];
|
||||||
|
ClientConfiguration clientConfiguration = ClientConfiguration.newBuilder()
|
||||||
|
.setEndpoints(endpoints)
|
||||||
|
.build();
|
||||||
|
String topic = args[1];
|
||||||
|
|
||||||
|
// Create the RocketMQ Producer.
|
||||||
|
final Producer producer = provider.newProducerBuilder()
|
||||||
|
.setClientConfiguration(clientConfiguration)
|
||||||
|
.setTopics(topic)
|
||||||
|
.build();
|
||||||
|
final Gson gson = new Gson();
|
||||||
|
Map<String, String> payload = new HashMap<>();
|
||||||
|
payload.put("foo", "bar");
|
||||||
|
final CloudEvent event = new CloudEventBuilder()
|
||||||
|
.withId("client-id")
|
||||||
|
.withSource(URI.create("http://127.0.0.1/rocketmq-client"))
|
||||||
|
.withType("com.foobar")
|
||||||
|
.withTime(Time.parseTime("2022-11-09T21:47:12.032198+00:00"))
|
||||||
|
.withData(gson.toJson(payload).getBytes(StandardCharsets.UTF_8))
|
||||||
|
.build();
|
||||||
|
// Transform event into message.
|
||||||
|
final Message message = RocketMqMessageFactory.createWriter(topic).writeBinary(event);
|
||||||
|
try {
|
||||||
|
// Send the message.
|
||||||
|
final SendReceipt sendReceipt = producer.send(message);
|
||||||
|
System.out.println("Send message successfully, messageId=" + sendReceipt.getMessageId());
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Failed to send message");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
// Close the producer when you don't need it anymore.
|
||||||
|
producer.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,9 +29,9 @@ It also accepts data in "structured" format:
|
||||||
```shell
|
```shell
|
||||||
curl -v -H'Content-type: application/cloudevents+json' \
|
curl -v -H'Content-type: application/cloudevents+json' \
|
||||||
-d '{"data": {"value": "Foo"},
|
-d '{"data": {"value": "Foo"},
|
||||||
"id: 1,
|
"id": "1",
|
||||||
"source": "cloud-event-example"
|
"source": "cloud-event-example",
|
||||||
"type": "my.application.Foo"
|
"type": "my.application.Foo",
|
||||||
"specversion": "1.0"}' \
|
"specversion": "1.0"}' \
|
||||||
http://localhost:8080/event
|
http://localhost:8080/event
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-spring-function-example</artifactId>
|
<artifactId>cloudevents-spring-function-example</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<module-name>cloudevents.example.spring.function</module-name>
|
||||||
<spring-boot.version>2.4.3</spring-boot.version>
|
<spring-boot.version>2.4.3</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
@ -4,22 +4,20 @@ import java.net.URI;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import io.cloudevents.CloudEvent;
|
|
||||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
|
||||||
import io.cloudevents.spring.messaging.CloudEventMessageConverter;
|
|
||||||
import io.cloudevents.spring.webflux.CloudEventHttpMessageReader;
|
|
||||||
import io.cloudevents.spring.webflux.CloudEventHttpMessageWriter;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.web.codec.CodecCustomizer;
|
import org.springframework.boot.web.codec.CodecCustomizer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.codec.CodecConfigurer;
|
import org.springframework.http.codec.CodecConfigurer;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import io.cloudevents.spring.messaging.CloudEventMessageConverter;
|
||||||
|
import io.cloudevents.spring.webflux.CloudEventHttpMessageReader;
|
||||||
|
import io.cloudevents.spring.webflux.CloudEventHttpMessageWriter;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@RestController
|
|
||||||
public class DemoApplication {
|
public class DemoApplication {
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
|
@ -5,13 +5,14 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-spring-reactive-example</artifactId>
|
<artifactId>cloudevents-spring-reactive-example</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<module-name>cloudevents.example.spring.reactive</module-name>
|
||||||
<spring-boot.version>2.4.3</spring-boot.version>
|
<spring-boot.version>2.4.3</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
@ -1,112 +1,149 @@
|
||||||
package io.cloudevents.examples.spring;
|
package io.cloudevents.examples.spring;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
import org.springframework.boot.test.web.client.TestRestTemplate;
|
|
||||||
import org.springframework.boot.web.server.LocalServerPort;
|
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.http.RequestEntity;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||||
public class DemoApplicationTests {
|
public class DemoApplicationTests {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private TestRestTemplate rest;
|
private WebTestClient rest;
|
||||||
|
|
||||||
@LocalServerPort
|
@Test
|
||||||
private int port;
|
void echoWithCorrectHeaders() {
|
||||||
|
|
||||||
@Test
|
rest.post().uri("/foos").header("ce-id", "12345") //
|
||||||
void echoWithCorrectHeaders() {
|
.header("ce-specversion", "1.0") //
|
||||||
|
.header("ce-type", "io.spring.event") //
|
||||||
|
.header("ce-source", "https://spring.io/events") //
|
||||||
|
.contentType(MediaType.APPLICATION_JSON) //
|
||||||
|
.bodyValue("{\"value\":\"Dave\"}") //
|
||||||
|
.exchange() //
|
||||||
|
.expectStatus().isOk() //
|
||||||
|
.expectHeader().exists("ce-id") //
|
||||||
|
.expectHeader().exists("ce-source") //
|
||||||
|
.expectHeader().exists("ce-type") //
|
||||||
|
.expectHeader().value("ce-id", value -> {
|
||||||
|
if (value.equals("12345"))
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}) //
|
||||||
|
.expectHeader().valueEquals("ce-type", "io.spring.event.Foo") //
|
||||||
|
.expectHeader().valueEquals("ce-source", "https://spring.io/foos") //
|
||||||
|
.expectBody(String.class).isEqualTo("{\"value\":\"Dave\"}");
|
||||||
|
|
||||||
ResponseEntity<String> response = rest
|
}
|
||||||
.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/foos")) //
|
|
||||||
.header("ce-id", "12345") //
|
|
||||||
.header("ce-specversion", "1.0") //
|
|
||||||
.header("ce-type", "io.spring.event") //
|
|
||||||
.header("ce-source", "https://spring.io/events") //
|
|
||||||
.contentType(MediaType.APPLICATION_JSON) //
|
|
||||||
.body("{\"value\":\"Dave\"}"), String.class);
|
|
||||||
|
|
||||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
@Test
|
||||||
assertThat(response.getBody()).isEqualTo("{\"value\":\"Dave\"}");
|
void structuredRequestResponseEvents() {
|
||||||
|
|
||||||
HttpHeaders headers = response.getHeaders();
|
rest.post().uri("/event") //
|
||||||
|
.contentType(new MediaType("application", "cloudevents+json")) //
|
||||||
|
.bodyValue("{" //
|
||||||
|
+ "\"id\":\"12345\"," //
|
||||||
|
+ "\"specversion\":\"1.0\"," //
|
||||||
|
+ "\"type\":\"io.spring.event\"," //
|
||||||
|
+ "\"source\":\"https://spring.io/events\"," //
|
||||||
|
+ "\"data\":{\"value\":\"Dave\"}}") //
|
||||||
|
.exchange() //
|
||||||
|
.expectStatus().isOk() //
|
||||||
|
.expectHeader().exists("ce-id") //
|
||||||
|
.expectHeader().exists("ce-source") //
|
||||||
|
.expectHeader().exists("ce-type") //
|
||||||
|
.expectHeader().value("ce-id", value -> {
|
||||||
|
if (value.equals("12345"))
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}) //
|
||||||
|
.expectHeader().valueEquals("ce-type", "io.spring.event.Foo") //
|
||||||
|
.expectHeader().valueEquals("ce-source", "https://spring.io/foos") //
|
||||||
|
.expectBody(String.class).isEqualTo("{\"value\":\"Dave\"}");
|
||||||
|
|
||||||
assertThat(headers).containsKey("ce-id");
|
}
|
||||||
assertThat(headers).containsKey("ce-source");
|
|
||||||
assertThat(headers).containsKey("ce-type");
|
|
||||||
|
|
||||||
assertThat(headers.getFirst("ce-id")).isNotEqualTo("12345");
|
@Test
|
||||||
assertThat(headers.getFirst("ce-type")).isEqualTo("io.spring.event.Foo");
|
void structuredRequestResponseCloudEventToString() {
|
||||||
assertThat(headers.getFirst("ce-source")).isEqualTo("https://spring.io/foos");
|
|
||||||
|
|
||||||
}
|
rest.post().uri("/event") //
|
||||||
|
.bodyValue(CloudEventBuilder.v1() //
|
||||||
|
.withId("12345") //
|
||||||
|
.withType("io.spring.event") //
|
||||||
|
.withSource(URI.create("https://spring.io/events")).withData("{\"value\":\"Dave\"}".getBytes(StandardCharsets.UTF_8)) //
|
||||||
|
.build()) //
|
||||||
|
.exchange() //
|
||||||
|
.expectStatus().isOk() //
|
||||||
|
.expectHeader().exists("ce-id") //
|
||||||
|
.expectHeader().exists("ce-source") //
|
||||||
|
.expectHeader().exists("ce-type") //
|
||||||
|
.expectHeader().value("ce-id", value -> {
|
||||||
|
if (value.equals("12345"))
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}) //
|
||||||
|
.expectHeader().valueEquals("ce-type", "io.spring.event.Foo") //
|
||||||
|
.expectHeader().valueEquals("ce-source", "https://spring.io/foos") //
|
||||||
|
.expectBody(String.class).isEqualTo("{\"value\":\"Dave\"}");
|
||||||
|
|
||||||
@Test
|
}
|
||||||
void structuredRequestResponseEvents() {
|
|
||||||
|
|
||||||
ResponseEntity<String> response = rest
|
@Test
|
||||||
.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/event")) //
|
void structuredRequestResponseCloudEventToCloudEvent() {
|
||||||
.contentType(new MediaType("application", "cloudevents+json")) //
|
|
||||||
.body("{" //
|
|
||||||
+ "\"id\":\"12345\"," //
|
|
||||||
+ "\"specversion\":\"1.0\"," //
|
|
||||||
+ "\"type\":\"io.spring.event\"," //
|
|
||||||
+ "\"source\":\"https://spring.io/events\"," //
|
|
||||||
+ "\"data\":{\"value\":\"Dave\"}}"),
|
|
||||||
String.class);
|
|
||||||
|
|
||||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
rest.post().uri("/event") //
|
||||||
assertThat(response.getBody()).isEqualTo("{\"value\":\"Dave\"}");
|
.accept(new MediaType("application", "cloudevents+json")) //
|
||||||
|
.bodyValue(CloudEventBuilder.v1() //
|
||||||
|
.withId("12345") //
|
||||||
|
.withType("io.spring.event") //
|
||||||
|
.withSource(URI.create("https://spring.io/events")) //
|
||||||
|
.withData("{\"value\":\"Dave\"}".getBytes(StandardCharsets.UTF_8)) //
|
||||||
|
.build()) //
|
||||||
|
.exchange() //
|
||||||
|
.expectStatus().isOk() //
|
||||||
|
.expectHeader().exists("ce-id") //
|
||||||
|
.expectHeader().exists("ce-source") //
|
||||||
|
.expectHeader().exists("ce-type") //
|
||||||
|
.expectHeader().value("ce-id", value -> {
|
||||||
|
if (value.equals("12345"))
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}) //
|
||||||
|
.expectHeader().valueEquals("ce-type", "io.spring.event.Foo") //
|
||||||
|
.expectHeader().valueEquals("ce-source", "https://spring.io/foos") //
|
||||||
|
.expectBody(CloudEvent.class) //
|
||||||
|
.value(event -> assertThat(new String(event.getData().toBytes())) //
|
||||||
|
.isEqualTo("{\"value\":\"Dave\"}"));
|
||||||
|
|
||||||
HttpHeaders headers = response.getHeaders();
|
}
|
||||||
|
|
||||||
assertThat(headers).containsKey("ce-id");
|
@Test
|
||||||
assertThat(headers).containsKey("ce-source");
|
void requestResponseEvents() {
|
||||||
assertThat(headers).containsKey("ce-type");
|
|
||||||
|
|
||||||
assertThat(headers.getFirst("ce-id")).isNotEqualTo("12345");
|
rest.post().uri("/event").header("ce-id", "12345") //
|
||||||
assertThat(headers.getFirst("ce-type")).isEqualTo("io.spring.event.Foo");
|
.header("ce-specversion", "1.0") //
|
||||||
assertThat(headers.getFirst("ce-source")).isEqualTo("https://spring.io/foos");
|
.header("ce-type", "io.spring.event") //
|
||||||
|
.header("ce-source", "https://spring.io/events") //
|
||||||
|
.contentType(MediaType.APPLICATION_JSON) //
|
||||||
|
.bodyValue("{\"value\":\"Dave\"}") //
|
||||||
|
.exchange() //
|
||||||
|
.expectStatus().isOk() //
|
||||||
|
.expectHeader().exists("ce-id") //
|
||||||
|
.expectHeader().exists("ce-source") //
|
||||||
|
.expectHeader().exists("ce-type") //
|
||||||
|
.expectHeader().value("ce-id", value -> {
|
||||||
|
if (value.equals("12345"))
|
||||||
|
throw new IllegalStateException();
|
||||||
|
}) //
|
||||||
|
.expectHeader().valueEquals("ce-type", "io.spring.event.Foo") //
|
||||||
|
.expectHeader().valueEquals("ce-source", "https://spring.io/foos") //
|
||||||
|
.expectBody(String.class).isEqualTo("{\"value\":\"Dave\"}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
void requestResponseEvents() {
|
|
||||||
|
|
||||||
ResponseEntity<String> response = rest
|
|
||||||
.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/event")) //
|
|
||||||
.header("ce-id", "12345") //
|
|
||||||
.header("ce-specversion", "1.0") //
|
|
||||||
.header("ce-type", "io.spring.event") //
|
|
||||||
.header("ce-source", "https://spring.io/events") //
|
|
||||||
.contentType(MediaType.APPLICATION_JSON) //
|
|
||||||
.body("{\"value\":\"Dave\"}"), String.class);
|
|
||||||
|
|
||||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
|
||||||
assertThat(response.getBody()).isEqualTo("{\"value\":\"Dave\"}");
|
|
||||||
|
|
||||||
HttpHeaders headers = response.getHeaders();
|
|
||||||
|
|
||||||
assertThat(headers).containsKey("ce-id");
|
|
||||||
assertThat(headers).containsKey("ce-source");
|
|
||||||
assertThat(headers).containsKey("ce-type");
|
|
||||||
|
|
||||||
assertThat(headers.getFirst("ce-id")).isNotEqualTo("12345");
|
|
||||||
assertThat(headers.getFirst("ce-type")).isEqualTo("io.spring.event.Foo");
|
|
||||||
assertThat(headers.getFirst("ce-source")).isEqualTo("https://spring.io/foos");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package io.cloudevents.examples.spring;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
|
||||||
|
import org.springframework.boot.web.server.LocalServerPort;
|
||||||
|
import org.springframework.web.reactive.function.client.WebClient;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test case to show example usage of WebClient and CloudEvent. The actual
|
||||||
|
* content of the request and response are asserted separately in
|
||||||
|
* {@link DemoApplicationTests}.
|
||||||
|
*/
|
||||||
|
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||||
|
public class WebClientTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WebClient.Builder rest;
|
||||||
|
|
||||||
|
@LocalServerPort
|
||||||
|
private int port;
|
||||||
|
|
||||||
|
private CloudEvent event;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
event = CloudEventBuilder.v1() //
|
||||||
|
.withId("12345") //
|
||||||
|
.withSource(URI.create("https://spring.io/events")) //
|
||||||
|
.withType("io.spring.event") //
|
||||||
|
.withData("{\"value\":\"Dave\"}".getBytes(StandardCharsets.UTF_8)) //
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void echoWithCorrectHeaders() {
|
||||||
|
|
||||||
|
Mono<CloudEvent> result = rest.build() //
|
||||||
|
.post() //
|
||||||
|
.uri("http://localhost:" + port + "/event") //
|
||||||
|
.bodyValue(event) //
|
||||||
|
.exchangeToMono(response -> response.bodyToMono(CloudEvent.class));
|
||||||
|
|
||||||
|
assertThat(result.block().getData()).isEqualTo(event.getData());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,13 +5,14 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-spring-rsocket-example</artifactId>
|
<artifactId>cloudevents-spring-rsocket-example</artifactId>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
|
<module-name>cloudevents.example.spring.rsocket</module-name>
|
||||||
<spring-boot.version>2.4.3</spring-boot.version>
|
<spring-boot.version>2.4.3</spring-boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
@ -5,11 +5,14 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>cloudevents-examples</artifactId>
|
<artifactId>cloudevents-examples</artifactId>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<artifactId>cloudevents-vertx-example</artifactId>
|
<artifactId>cloudevents-vertx-example</artifactId>
|
||||||
|
<properties>
|
||||||
|
<module-name>cloudevents.example.vertx</module-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -12,6 +12,7 @@ import io.vertx.ext.web.client.HttpResponse;
|
||||||
import io.vertx.ext.web.client.WebClient;
|
import io.vertx.ext.web.client.WebClient;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class SampleHTTPClient {
|
public class SampleHTTPClient {
|
||||||
|
@ -31,7 +32,7 @@ public class SampleHTTPClient {
|
||||||
|
|
||||||
// Create an event template to set basic CloudEvent attributes.
|
// Create an event template to set basic CloudEvent attributes.
|
||||||
CloudEventBuilder eventTemplate = CloudEventBuilder.v1()
|
CloudEventBuilder eventTemplate = CloudEventBuilder.v1()
|
||||||
.withSource(URI.create("https://github.com/cloudevents/sdk-java/tree/master/examples/vertx"))
|
.withSource(URI.create("https://github.com/cloudevents/sdk-java/tree/main/examples/vertx"))
|
||||||
.withType("vertx.example");
|
.withType("vertx.example");
|
||||||
|
|
||||||
// Send NUM_EVENTS events.
|
// Send NUM_EVENTS events.
|
||||||
|
@ -41,7 +42,7 @@ public class SampleHTTPClient {
|
||||||
// Create the event starting from the template
|
// Create the event starting from the template
|
||||||
final CloudEvent event = eventTemplate.newBuilder()
|
final CloudEvent event = eventTemplate.newBuilder()
|
||||||
.withId(UUID.randomUUID().toString())
|
.withId(UUID.randomUUID().toString())
|
||||||
.withData("text/plain", data.getBytes())
|
.withData("text/plain", data.getBytes(StandardCharsets.UTF_8))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
Future<HttpResponse<Buffer>> responseFuture;
|
Future<HttpResponse<Buffer>> responseFuture;
|
||||||
|
|
|
@ -23,7 +23,7 @@ public class SampleHTTPServer {
|
||||||
|
|
||||||
// We need to read the event from the HTTP request we get, so create a MessageReader.
|
// We need to read the event from the HTTP request we get, so create a MessageReader.
|
||||||
VertxMessageFactory.createReader(request)
|
VertxMessageFactory.createReader(request)
|
||||||
// Covert the MessageReader to a CloudEvent.
|
// Convert the MessageReader to a CloudEvent.
|
||||||
.map(MessageReader::toEvent)
|
.map(MessageReader::toEvent)
|
||||||
.onSuccess(event -> {
|
.onSuccess(event -> {
|
||||||
// Print out the event.
|
// Print out the event.
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!--
|
||||||
|
~ Copyright 2021-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>
|
||||||
|
<relativePath>../../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>cloudevents-avro-compact</artifactId>
|
||||||
|
<name>CloudEvents - Avro Compact</name>
|
||||||
|
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<module-name>io.cloudevents.formats.avro.compact</module-name>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.avro</groupId>
|
||||||
|
<artifactId>avro-maven-plugin</artifactId>
|
||||||
|
<version>1.11.2</version>
|
||||||
|
<configuration>
|
||||||
|
<stringType>String</stringType>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>schema</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<artifactId>cloudevents-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.avro</groupId>
|
||||||
|
<artifactId>avro</artifactId>
|
||||||
|
<version>1.11.4</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Test deps -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>2.0.16</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit-jupiter.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<version>${assertj-core.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.cloudevents</groupId>
|
||||||
|
<artifactId>cloudevents-core</artifactId>
|
||||||
|
<classifier>tests</classifier>
|
||||||
|
<type>test-jar</type>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,81 @@
|
||||||
|
{
|
||||||
|
"namespace": "io.cloudevents.v1.avro.compact",
|
||||||
|
"type": "record",
|
||||||
|
"name": "CloudEvent",
|
||||||
|
"version": "1.0",
|
||||||
|
"doc": "Avro Compact Event Format for CloudEvents",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "source",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "type",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "datacontenttype",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dataschema",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "subject",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
"string"
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "time",
|
||||||
|
"type": [
|
||||||
|
"null",
|
||||||
|
{
|
||||||
|
"type": "long",
|
||||||
|
"logicalType": "timestamp-micros"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "extensions",
|
||||||
|
"type": {
|
||||||
|
"type": "map",
|
||||||
|
"values": [
|
||||||
|
"boolean",
|
||||||
|
"int",
|
||||||
|
{
|
||||||
|
"type": "long",
|
||||||
|
"logicalType" : "timestamp-micros"
|
||||||
|
},
|
||||||
|
"string",
|
||||||
|
"bytes"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "data",
|
||||||
|
"type": [
|
||||||
|
"bytes",
|
||||||
|
"null"
|
||||||
|
],
|
||||||
|
"default": "null"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
/*
|
||||||
|
* 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.avro.compact;
|
||||||
|
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.CloudEventData;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import io.cloudevents.core.data.BytesCloudEventData;
|
||||||
|
import io.cloudevents.core.format.EventDeserializationException;
|
||||||
|
import io.cloudevents.core.format.EventFormat;
|
||||||
|
import io.cloudevents.core.format.EventSerializationException;
|
||||||
|
import io.cloudevents.rw.CloudEventDataMapper;
|
||||||
|
import io.cloudevents.v1.avro.compact.CloudEvent.Builder;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.OffsetDateTime;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of {@link EventFormat} for the Avro Compact format.
|
||||||
|
* This format is resolvable with {@link io.cloudevents.core.provider.EventFormatProvider} using the content type {@link #AVRO_COMPACT_CONTENT_TYPE}.
|
||||||
|
*/
|
||||||
|
public class AvroCompactFormat implements EventFormat {
|
||||||
|
|
||||||
|
public static final String AVRO_COMPACT_CONTENT_TYPE = "application/cloudevents+avrocompact";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] serialize(CloudEvent from) throws EventSerializationException {
|
||||||
|
try {
|
||||||
|
Builder to = io.cloudevents.v1.avro.compact.CloudEvent.newBuilder();
|
||||||
|
|
||||||
|
// extensions
|
||||||
|
Map<String, Object> extensions = new HashMap<>();
|
||||||
|
for (String name : from.getExtensionNames()) {
|
||||||
|
Object value = from.getExtension(name);
|
||||||
|
if (value instanceof byte[])
|
||||||
|
value = ByteBuffer.wrap((byte[]) value);
|
||||||
|
else if (value instanceof OffsetDateTime)
|
||||||
|
value = ((OffsetDateTime) value).toInstant();
|
||||||
|
extensions.put(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
to.setSource(from.getSource().toString())
|
||||||
|
.setType(from.getType())
|
||||||
|
.setId(from.getId())
|
||||||
|
.setSubject(from.getSubject())
|
||||||
|
.setDatacontenttype(from.getDataContentType())
|
||||||
|
.setExtensions(extensions);
|
||||||
|
|
||||||
|
if (from.getTime() != null)
|
||||||
|
to.setTime(from.getTime().toInstant());
|
||||||
|
if (from.getDataSchema() != null)
|
||||||
|
to.setDataschema(from.getDataSchema().toString());
|
||||||
|
|
||||||
|
CloudEventData data = from.getData();
|
||||||
|
if (data != null)
|
||||||
|
to.setData(ByteBuffer.wrap(data.toBytes()));
|
||||||
|
return to.build().toByteBuffer().array();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new EventSerializationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper<? extends CloudEventData> mapper) throws EventDeserializationException {
|
||||||
|
try {
|
||||||
|
io.cloudevents.v1.avro.compact.CloudEvent from = io.cloudevents.v1.avro.compact.CloudEvent.fromByteBuffer(ByteBuffer.wrap(bytes));
|
||||||
|
CloudEventBuilder to = CloudEventBuilder.v1()
|
||||||
|
.withSource(URI.create(from.getSource()))
|
||||||
|
.withType(from.getType())
|
||||||
|
.withId(from.getType())
|
||||||
|
.withSubject(from.getSubject())
|
||||||
|
.withDataContentType(from.getDatacontenttype());
|
||||||
|
|
||||||
|
if (from.getTime() != null)
|
||||||
|
to.withTime(from.getTime().atOffset(ZoneOffset.UTC));
|
||||||
|
if (from.getDataschema() != null)
|
||||||
|
to.withDataSchema(URI.create(from.getDataschema()));
|
||||||
|
|
||||||
|
// extensions
|
||||||
|
for (Map.Entry<String, Object> entry : from.getExtensions().entrySet()) {
|
||||||
|
String name = entry.getKey();
|
||||||
|
Object value = entry.getValue();
|
||||||
|
// Avro supports boolean, int, string, bytes
|
||||||
|
if (value instanceof Boolean)
|
||||||
|
to.withExtension(name, (boolean) value);
|
||||||
|
else if (value instanceof Integer)
|
||||||
|
to.withExtension(name, (int) value);
|
||||||
|
else if (value instanceof Instant)
|
||||||
|
to.withExtension(name, ((Instant) value).atOffset(ZoneOffset.UTC));
|
||||||
|
else if (value instanceof String)
|
||||||
|
to.withExtension(name, (String) value);
|
||||||
|
else if (value instanceof ByteBuffer)
|
||||||
|
to.withExtension(name, ((ByteBuffer) value).array());
|
||||||
|
else
|
||||||
|
// this cannot happen, if ever seen, must be bug in this library
|
||||||
|
throw new AssertionError(String.format("invalid extension %s unsupported type %s", name, value.getClass()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from.getData() == null)
|
||||||
|
return to.end();
|
||||||
|
else {
|
||||||
|
CloudEventData data = BytesCloudEventData.wrap(from.getData().array());
|
||||||
|
return to.end(mapper.map(data));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new EventDeserializationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String serializedContentType() {
|
||||||
|
return AVRO_COMPACT_CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
io.cloudevents.avro.compact.AvroCompactFormat
|
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
* 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.avro.compact;
|
||||||
|
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import io.cloudevents.core.data.BytesCloudEventData;
|
||||||
|
import io.cloudevents.core.format.EventFormat;
|
||||||
|
import io.cloudevents.core.provider.EventFormatProvider;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
|
||||||
|
class AvroCompactFormatTest {
|
||||||
|
|
||||||
|
private final EventFormat format = EventFormatProvider.getInstance().resolveFormat(AvroCompactFormat.AVRO_COMPACT_CONTENT_TYPE);
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void format() {
|
||||||
|
assertNotNull(format);
|
||||||
|
assertEquals(Collections.singleton("application/cloudevents+avrocompact"), format.deserializableContentTypes());
|
||||||
|
|
||||||
|
CloudEvent event = CloudEventBuilder.v1()
|
||||||
|
// mandatory
|
||||||
|
.withId("")
|
||||||
|
.withSource(URI.create(""))
|
||||||
|
.withType("")
|
||||||
|
// optional
|
||||||
|
.withTime(Instant.EPOCH.atOffset(ZoneOffset.UTC))
|
||||||
|
.withSubject("subject")
|
||||||
|
.withDataSchema(URI.create(""))
|
||||||
|
// extension
|
||||||
|
// support boolean, int, long, string, bytes
|
||||||
|
.withExtension("boolean", false)
|
||||||
|
.withExtension("int", 0)
|
||||||
|
.withExtension("time", Instant.EPOCH.atOffset(ZoneOffset.UTC))
|
||||||
|
.withExtension("string", "")
|
||||||
|
// omitting bytes, because it is not supported by CloudEvent.equals
|
||||||
|
.withData("", BytesCloudEventData.wrap(new byte[0]))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
byte[] serialized = format.serialize(event);
|
||||||
|
|
||||||
|
assertNotNull(serialized);
|
||||||
|
|
||||||
|
CloudEvent deserialized = format.deserialize(serialized);
|
||||||
|
|
||||||
|
assertEquals(event, deserialized);
|
||||||
|
|
||||||
|
byte[] reserialized = format.serialize(deserialized);
|
||||||
|
|
||||||
|
assertArrayEquals(serialized, reserialized);
|
||||||
|
}
|
||||||
|
}
|
|
@ -22,7 +22,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>io.cloudevents</groupId>
|
<groupId>io.cloudevents</groupId>
|
||||||
<artifactId>cloudevents-parent</artifactId>
|
<artifactId>cloudevents-parent</artifactId>
|
||||||
<version>2.1.0</version>
|
<version>4.1.0-SNAPSHOT</version>
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<jackson.version>2.11.2</jackson.version>
|
<jackson.version>2.15.2</jackson.version>
|
||||||
<module-name>io.cloudevents.formats.jackson</module-name>
|
<module-name>io.cloudevents.formats.jackson</module-name>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
import com.fasterxml.jackson.databind.node.JsonNodeType;
|
||||||
|
import com.fasterxml.jackson.databind.node.NullNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import io.cloudevents.CloudEvent;
|
import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.CloudEventData;
|
import io.cloudevents.CloudEventData;
|
||||||
|
@ -32,25 +33,52 @@ import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
import io.cloudevents.core.data.BytesCloudEventData;
|
import io.cloudevents.core.data.BytesCloudEventData;
|
||||||
import io.cloudevents.rw.*;
|
import io.cloudevents.rw.*;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jackson {@link com.fasterxml.jackson.databind.JsonDeserializer} for {@link CloudEvent}
|
* Jackson {@link com.fasterxml.jackson.databind.JsonDeserializer} for {@link CloudEvent}
|
||||||
*/
|
*/
|
||||||
class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
|
private final boolean forceExtensionNameLowerCaseDeserialization;
|
||||||
|
private final boolean forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
private final boolean disableDataContentTypeDefaulting;
|
||||||
|
|
||||||
protected CloudEventDeserializer() {
|
protected CloudEventDeserializer() {
|
||||||
|
this(false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CloudEventDeserializer(
|
||||||
|
boolean forceExtensionNameLowerCaseDeserialization,
|
||||||
|
boolean forceIgnoreInvalidExtensionNameDeserialization,
|
||||||
|
boolean disableDataContentTypeDefaulting
|
||||||
|
) {
|
||||||
super(CloudEvent.class);
|
super(CloudEvent.class);
|
||||||
|
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
|
||||||
|
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class JsonMessage implements CloudEventReader {
|
private static class JsonMessage implements CloudEventReader {
|
||||||
|
|
||||||
private final JsonParser p;
|
private final JsonParser p;
|
||||||
private final ObjectNode node;
|
private final ObjectNode node;
|
||||||
|
private final boolean forceExtensionNameLowerCaseDeserialization;
|
||||||
|
private final boolean forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
private final boolean disableDataContentTypeDefaulting;
|
||||||
|
|
||||||
public JsonMessage(JsonParser p, ObjectNode node) {
|
public JsonMessage(
|
||||||
|
JsonParser p,
|
||||||
|
ObjectNode node,
|
||||||
|
boolean forceExtensionNameLowerCaseDeserialization,
|
||||||
|
boolean forceIgnoreInvalidExtensionNameDeserialization,
|
||||||
|
boolean disableDataContentTypeDefaulting
|
||||||
|
) {
|
||||||
this.p = p;
|
this.p = p;
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
|
||||||
|
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,6 +98,9 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
|
|
||||||
// Parse datacontenttype if any
|
// Parse datacontenttype if any
|
||||||
String contentType = getOptionalStringNode(this.node, this.p, "datacontenttype");
|
String contentType = getOptionalStringNode(this.node, this.p, "datacontenttype");
|
||||||
|
if (!this.disableDataContentTypeDefaulting && contentType == null && this.node.has("data")) {
|
||||||
|
contentType = "application/json";
|
||||||
|
}
|
||||||
if (contentType != null) {
|
if (contentType != null) {
|
||||||
writer.withContextAttribute("datacontenttype", contentType);
|
writer.withContextAttribute("datacontenttype", contentType);
|
||||||
}
|
}
|
||||||
|
@ -97,11 +128,11 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
if (JsonFormat.dataIsJsonContentType(contentType)) {
|
if (JsonFormat.dataIsJsonContentType(contentType)) {
|
||||||
// This solution is quite bad, but i see no alternatives now.
|
// This solution is quite bad, but i see no alternatives now.
|
||||||
// Hopefully in future we can improve it
|
// Hopefully in future we can improve it
|
||||||
data = new JsonCloudEventData(node.remove("data"));
|
data = JsonCloudEventData.wrap(node.remove("data"));
|
||||||
} else {
|
} else {
|
||||||
JsonNode dataNode = node.remove("data");
|
JsonNode dataNode = node.remove("data");
|
||||||
assertNodeType(dataNode, JsonNodeType.STRING, "data", "Because content type is not a json, only a string is accepted as data");
|
assertNodeType(dataNode, JsonNodeType.STRING, "data", "Because content type is not a json, only a string is accepted as data");
|
||||||
data = BytesCloudEventData.wrap(dataNode.asText().getBytes());
|
data = BytesCloudEventData.wrap(dataNode.asText().getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -115,11 +146,11 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
if (JsonFormat.dataIsJsonContentType(contentType)) {
|
if (JsonFormat.dataIsJsonContentType(contentType)) {
|
||||||
// This solution is quite bad, but i see no alternatives now.
|
// This solution is quite bad, but i see no alternatives now.
|
||||||
// Hopefully in future we can improve it
|
// Hopefully in future we can improve it
|
||||||
data = new JsonCloudEventData(node.remove("data"));
|
data = JsonCloudEventData.wrap(node.remove("data"));
|
||||||
} else {
|
} else {
|
||||||
JsonNode dataNode = node.remove("data");
|
JsonNode dataNode = node.remove("data");
|
||||||
assertNodeType(dataNode, JsonNodeType.STRING, "data", "Because content type is not a json, only a string is accepted as data");
|
assertNodeType(dataNode, JsonNodeType.STRING, "data", "Because content type is not a json, only a string is accepted as data");
|
||||||
data = BytesCloudEventData.wrap(dataNode.asText().getBytes());
|
data = BytesCloudEventData.wrap(dataNode.asText().getBytes(StandardCharsets.UTF_8));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -127,6 +158,14 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
// Now let's process the extensions
|
// Now let's process the extensions
|
||||||
node.fields().forEachRemaining(entry -> {
|
node.fields().forEachRemaining(entry -> {
|
||||||
String extensionName = entry.getKey();
|
String extensionName = entry.getKey();
|
||||||
|
if (this.forceExtensionNameLowerCaseDeserialization) {
|
||||||
|
extensionName = extensionName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.shouldSkipExtensionName(extensionName)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
JsonNode extensionValue = entry.getValue();
|
JsonNode extensionValue = entry.getValue();
|
||||||
|
|
||||||
switch (extensionValue.getNodeType()) {
|
switch (extensionValue.getNodeType()) {
|
||||||
|
@ -175,12 +214,12 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getOptionalStringNode(ObjectNode objNode, JsonParser p, String attributeName) throws JsonProcessingException {
|
private String getOptionalStringNode(ObjectNode objNode, JsonParser p, String attributeName) throws JsonProcessingException {
|
||||||
JsonNode unparsedSpecVersion = objNode.remove(attributeName);
|
JsonNode unparsedAttribute = objNode.remove(attributeName);
|
||||||
if (unparsedSpecVersion == null) {
|
if (unparsedAttribute == null || unparsedAttribute instanceof NullNode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
assertNodeType(unparsedSpecVersion, JsonNodeType.STRING, attributeName, null);
|
assertNodeType(unparsedAttribute, JsonNodeType.STRING, attributeName, null);
|
||||||
return unparsedSpecVersion.asText();
|
return unparsedAttribute.asText();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertNodeType(JsonNode node, JsonNodeType type, String attributeName, String desc) throws JsonProcessingException {
|
private void assertNodeType(JsonNode node, JsonNodeType type, String attributeName, String desc) throws JsonProcessingException {
|
||||||
|
@ -192,6 +231,32 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore not valid extension name
|
||||||
|
private boolean shouldSkipExtensionName(String extensionName) {
|
||||||
|
return this.forceIgnoreInvalidExtensionNameDeserialization && !this.isValidExtensionName(extensionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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/spec.md#attribute-naming-convention">attribute-naming-convention</a>
|
||||||
|
*/
|
||||||
|
private boolean isValidExtensionName(String name) {
|
||||||
|
for (int i = 0; i < name.length(); i++) {
|
||||||
|
if (!isValidChar(name.charAt(i))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidChar(char c) {
|
||||||
|
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -201,7 +266,8 @@ class CloudEventDeserializer extends StdDeserializer<CloudEvent> {
|
||||||
ObjectNode node = ctxt.readValue(p, ObjectNode.class);
|
ObjectNode node = ctxt.readValue(p, ObjectNode.class);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return new JsonMessage(p, node).read(CloudEventBuilder::fromSpecVersion);
|
return new JsonMessage(p, node, this.forceExtensionNameLowerCaseDeserialization, this.forceIgnoreInvalidExtensionNameDeserialization, this.disableDataContentTypeDefaulting)
|
||||||
|
.read(CloudEventBuilder::fromSpecVersion);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// Yeah this is bad but it's needed to support checked exceptions...
|
// Yeah this is bad but it's needed to support checked exceptions...
|
||||||
if (e.getCause() instanceof IOException) {
|
if (e.getCause() instanceof IOException) {
|
||||||
|
|
|
@ -20,6 +20,7 @@ package io.cloudevents.jackson;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import io.cloudevents.CloudEventData;
|
import io.cloudevents.CloudEventData;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -40,7 +41,7 @@ public class JsonCloudEventData implements CloudEventData {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] toBytes() {
|
public byte[] toBytes() {
|
||||||
return node.toString().getBytes();
|
return node.toString().getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
import io.cloudevents.CloudEvent;
|
import io.cloudevents.CloudEvent;
|
||||||
import io.cloudevents.CloudEventData;
|
import io.cloudevents.CloudEventData;
|
||||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||||
|
import io.cloudevents.core.format.ContentType;
|
||||||
import io.cloudevents.core.format.EventDeserializationException;
|
import io.cloudevents.core.format.EventDeserializationException;
|
||||||
import io.cloudevents.core.format.EventFormat;
|
import io.cloudevents.core.format.EventFormat;
|
||||||
import io.cloudevents.core.format.EventSerializationException;
|
import io.cloudevents.core.format.EventSerializationException;
|
||||||
|
@ -29,6 +30,7 @@ import io.cloudevents.rw.CloudEventDataMapper;
|
||||||
import io.cloudevents.rw.CloudEventRWException;
|
import io.cloudevents.rw.CloudEventRWException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link EventFormat} for <a href="https://github.com/cloudevents/spec/blob/v1.0/json-format.md">JSON event format</a>
|
* Implementation of {@link EventFormat} for <a href="https://github.com/cloudevents/spec/blob/v1.0/json-format.md">JSON event format</a>
|
||||||
|
@ -43,10 +45,12 @@ public final class JsonFormat implements EventFormat {
|
||||||
* Content type associated with the JSON event format
|
* Content type associated with the JSON event format
|
||||||
*/
|
*/
|
||||||
public static final String CONTENT_TYPE = "application/cloudevents+json";
|
public static final String CONTENT_TYPE = "application/cloudevents+json";
|
||||||
|
/**
|
||||||
|
* JSON Data Content Type Discriminator
|
||||||
|
*/
|
||||||
|
private static final Pattern JSON_CONTENT_TYPE_PATTERN = Pattern.compile("^(application|text)\\/([a-zA-Z]+\\+)?json(;.*)*$");
|
||||||
private final ObjectMapper mapper;
|
private final ObjectMapper mapper;
|
||||||
private final boolean forceDataBase64Serialization;
|
private final JsonFormatOptions options;
|
||||||
private final boolean forceStringSerialization;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance of this class customizing the serialization configuration.
|
* Create a new instance of this class customizing the serialization configuration.
|
||||||
|
@ -57,31 +61,86 @@ public final class JsonFormat implements EventFormat {
|
||||||
* @see #withForceNonJsonDataToString()
|
* @see #withForceNonJsonDataToString()
|
||||||
*/
|
*/
|
||||||
public JsonFormat(boolean forceDataBase64Serialization, boolean forceStringSerialization) {
|
public JsonFormat(boolean forceDataBase64Serialization, boolean forceStringSerialization) {
|
||||||
|
this(
|
||||||
|
JsonFormatOptions.builder()
|
||||||
|
.forceDataBase64Serialization(forceDataBase64Serialization)
|
||||||
|
.forceStringSerialization(forceStringSerialization)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of this class customizing the serialization configuration.
|
||||||
|
*
|
||||||
|
* @param options json serialization / deserialization options
|
||||||
|
*/
|
||||||
|
public JsonFormat(JsonFormatOptions options) {
|
||||||
this.mapper = new ObjectMapper();
|
this.mapper = new ObjectMapper();
|
||||||
this.mapper.registerModule(getCloudEventJacksonModule(forceDataBase64Serialization, forceStringSerialization));
|
this.mapper.registerModule(getCloudEventJacksonModule(options));
|
||||||
this.forceDataBase64Serialization = forceDataBase64Serialization;
|
this.options = options;
|
||||||
this.forceStringSerialization = forceStringSerialization;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new instance of this class with default serialization configuration
|
* Create a new instance of this class with default serialization configuration
|
||||||
*/
|
*/
|
||||||
public JsonFormat() {
|
public JsonFormat() {
|
||||||
this(false, false);
|
this(new JsonFormatOptions());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a copy of this JsonFormat that serialize events with json data with Base64 encoding
|
* @return a copy of this JsonFormat that serialize events with json data with Base64 encoding
|
||||||
*/
|
*/
|
||||||
public JsonFormat withForceJsonDataToBase64() {
|
public JsonFormat withForceJsonDataToBase64() {
|
||||||
return new JsonFormat(true, this.forceStringSerialization);
|
return new JsonFormat(
|
||||||
|
JsonFormatOptions.builder()
|
||||||
|
.forceDataBase64Serialization(true)
|
||||||
|
.forceStringSerialization(this.options.isForceStringSerialization())
|
||||||
|
.forceExtensionNameLowerCaseDeserialization(this.options.isForceExtensionNameLowerCaseDeserialization())
|
||||||
|
.forceIgnoreInvalidExtensionNameDeserialization(this.options.isForceIgnoreInvalidExtensionNameDeserialization())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return a copy of this JsonFormat that serialize events with non-json data as string
|
* @return a copy of this JsonFormat that serialize events with non-json data as string
|
||||||
*/
|
*/
|
||||||
public JsonFormat withForceNonJsonDataToString() {
|
public JsonFormat withForceNonJsonDataToString() {
|
||||||
return new JsonFormat(this.forceDataBase64Serialization, true);
|
return new JsonFormat(
|
||||||
|
JsonFormatOptions.builder()
|
||||||
|
.forceDataBase64Serialization(this.options.isForceDataBase64Serialization())
|
||||||
|
.forceStringSerialization(true)
|
||||||
|
.forceExtensionNameLowerCaseDeserialization(this.options.isForceExtensionNameLowerCaseDeserialization())
|
||||||
|
.forceIgnoreInvalidExtensionNameDeserialization(this.options.isForceIgnoreInvalidExtensionNameDeserialization())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a copy of this JsonFormat that deserialize events with converting extension name lower case.
|
||||||
|
*/
|
||||||
|
public JsonFormat withForceExtensionNameLowerCaseDeserialization() {
|
||||||
|
return new JsonFormat(
|
||||||
|
JsonFormatOptions.builder()
|
||||||
|
.forceDataBase64Serialization(this.options.isForceDataBase64Serialization())
|
||||||
|
.forceStringSerialization(this.options.isForceStringSerialization())
|
||||||
|
.forceExtensionNameLowerCaseDeserialization(true)
|
||||||
|
.forceIgnoreInvalidExtensionNameDeserialization(this.options.isForceIgnoreInvalidExtensionNameDeserialization())
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a copy of this JsonFormat that deserialize events with ignoring invalid extension name
|
||||||
|
*/
|
||||||
|
public JsonFormat withForceIgnoreInvalidExtensionNameDeserialization() {
|
||||||
|
return new JsonFormat(
|
||||||
|
JsonFormatOptions.builder()
|
||||||
|
.forceDataBase64Serialization(this.options.isForceDataBase64Serialization())
|
||||||
|
.forceStringSerialization(this.options.isForceStringSerialization())
|
||||||
|
.forceExtensionNameLowerCaseDeserialization(this.options.isForceExtensionNameLowerCaseDeserialization())
|
||||||
|
.forceIgnoreInvalidExtensionNameDeserialization(true)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -137,14 +196,29 @@ public final class JsonFormat implements EventFormat {
|
||||||
* @see #withForceNonJsonDataToString()
|
* @see #withForceNonJsonDataToString()
|
||||||
*/
|
*/
|
||||||
public static SimpleModule getCloudEventJacksonModule(boolean forceDataBase64Serialization, boolean forceStringSerialization) {
|
public static SimpleModule getCloudEventJacksonModule(boolean forceDataBase64Serialization, boolean forceStringSerialization) {
|
||||||
|
return getCloudEventJacksonModule(
|
||||||
|
JsonFormatOptions.builder()
|
||||||
|
.forceDataBase64Serialization(forceDataBase64Serialization)
|
||||||
|
.forceStringSerialization(forceStringSerialization)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param options json serialization / deserialization options
|
||||||
|
* @return a JacksonModule with CloudEvent serializer/deserializer customizing the data serialization.
|
||||||
|
*/
|
||||||
|
public static SimpleModule getCloudEventJacksonModule(JsonFormatOptions options) {
|
||||||
final SimpleModule ceModule = new SimpleModule("CloudEvent");
|
final SimpleModule ceModule = new SimpleModule("CloudEvent");
|
||||||
ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer(forceDataBase64Serialization, forceStringSerialization));
|
ceModule.addSerializer(CloudEvent.class, new CloudEventSerializer(
|
||||||
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer());
|
options.isForceDataBase64Serialization(), options.isForceStringSerialization()));
|
||||||
|
ceModule.addDeserializer(CloudEvent.class, new CloudEventDeserializer(
|
||||||
|
options.isForceExtensionNameLowerCaseDeserialization(), options.isForceIgnoreInvalidExtensionNameDeserialization(), options.isDataContentTypeDefaultingDisabled()));
|
||||||
return ceModule;
|
return ceModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean dataIsJsonContentType(String contentType) {
|
static boolean dataIsJsonContentType(String contentType) {
|
||||||
// If content type, spec states that we should assume is json
|
// If content type, spec states that we should assume is json
|
||||||
return contentType == null || contentType.startsWith("application/json") || contentType.startsWith("text/json");
|
return contentType == null || JSON_CONTENT_TYPE_PATTERN.matcher(contentType).matches();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,111 @@
|
||||||
|
/*
|
||||||
|
* 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.jackson;
|
||||||
|
|
||||||
|
public final class JsonFormatOptions {
|
||||||
|
private final boolean forceDataBase64Serialization;
|
||||||
|
private final boolean forceStringSerialization;
|
||||||
|
private final boolean forceExtensionNameLowerCaseDeserialization;
|
||||||
|
private final boolean forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
private final boolean disableDataContentTypeDefaulting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of this class options the serialization / deserialization.
|
||||||
|
*/
|
||||||
|
public JsonFormatOptions() {
|
||||||
|
this(false, false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonFormatOptions(
|
||||||
|
boolean forceDataBase64Serialization,
|
||||||
|
boolean forceStringSerialization,
|
||||||
|
boolean forceExtensionNameLowerCaseDeserialization,
|
||||||
|
boolean forceIgnoreInvalidExtensionNameDeserialization,
|
||||||
|
boolean disableDataContentTypeDefaulting
|
||||||
|
) {
|
||||||
|
this.forceDataBase64Serialization = forceDataBase64Serialization;
|
||||||
|
this.forceStringSerialization = forceStringSerialization;
|
||||||
|
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
|
||||||
|
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonFormatOptionsBuilder builder() {
|
||||||
|
return new JsonFormatOptionsBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForceDataBase64Serialization() {
|
||||||
|
return this.forceDataBase64Serialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForceStringSerialization() {
|
||||||
|
return this.forceStringSerialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForceExtensionNameLowerCaseDeserialization() {
|
||||||
|
return this.forceExtensionNameLowerCaseDeserialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isForceIgnoreInvalidExtensionNameDeserialization() {
|
||||||
|
return this.forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDataContentTypeDefaultingDisabled() { return this.disableDataContentTypeDefaulting; }
|
||||||
|
|
||||||
|
public static class JsonFormatOptionsBuilder {
|
||||||
|
private boolean forceDataBase64Serialization = false;
|
||||||
|
private boolean forceStringSerialization = false;
|
||||||
|
private boolean forceExtensionNameLowerCaseDeserialization = false;
|
||||||
|
private boolean forceIgnoreInvalidExtensionNameDeserialization = false;
|
||||||
|
private boolean disableDataContentTypeDefaulting = false;
|
||||||
|
|
||||||
|
public JsonFormatOptionsBuilder forceDataBase64Serialization(boolean forceDataBase64Serialization) {
|
||||||
|
this.forceDataBase64Serialization = forceDataBase64Serialization;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonFormatOptionsBuilder forceStringSerialization(boolean forceStringSerialization) {
|
||||||
|
this.forceStringSerialization = forceStringSerialization;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonFormatOptionsBuilder forceExtensionNameLowerCaseDeserialization(boolean forceExtensionNameLowerCaseDeserialization) {
|
||||||
|
this.forceExtensionNameLowerCaseDeserialization = forceExtensionNameLowerCaseDeserialization;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonFormatOptionsBuilder forceIgnoreInvalidExtensionNameDeserialization(boolean forceIgnoreInvalidExtensionNameDeserialization) {
|
||||||
|
this.forceIgnoreInvalidExtensionNameDeserialization = forceIgnoreInvalidExtensionNameDeserialization;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonFormatOptionsBuilder disableDataContentTypeDefaulting(boolean disableDataContentTypeDefaulting) {
|
||||||
|
this.disableDataContentTypeDefaulting = disableDataContentTypeDefaulting;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonFormatOptions build() {
|
||||||
|
return new JsonFormatOptions(
|
||||||
|
this.forceDataBase64Serialization,
|
||||||
|
this.forceStringSerialization,
|
||||||
|
this.forceExtensionNameLowerCaseDeserialization,
|
||||||
|
this.forceIgnoreInvalidExtensionNameDeserialization,
|
||||||
|
this.disableDataContentTypeDefaulting
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package io.cloudevents.jackson;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import io.cloudevents.CloudEvent;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
|
||||||
|
import static io.cloudevents.jackson.JsonFormat.getCloudEventJacksonModule;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
public class CloudEventDeserializerTest {
|
||||||
|
|
||||||
|
private static final String nonBinaryPayload = "{\n" +
|
||||||
|
" \"specversion\" : \"1.0\",\n" +
|
||||||
|
" \"type\" : \"com.example.someevent\",\n" +
|
||||||
|
" \"source\" : \"/mycontext\",\n" +
|
||||||
|
" \"subject\": null,\n" +
|
||||||
|
" \"id\" : \"D234-1234-1234\",\n" +
|
||||||
|
" \"time\" : \"2018-04-05T17:31:00Z\",\n" +
|
||||||
|
" \"comexampleextension1\" : \"value\",\n" +
|
||||||
|
" \"comexampleothervalue\" : 5,\n" +
|
||||||
|
" \"data\" : \"I'm just a string\"\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
private static final String binaryPayload = "{\n" +
|
||||||
|
" \"specversion\" : \"1.0\",\n" +
|
||||||
|
" \"type\" : \"com.example.someevent\",\n" +
|
||||||
|
" \"source\" : \"/mycontext\",\n" +
|
||||||
|
" \"id\" : \"D234-1234-1234\",\n" +
|
||||||
|
" \"data_base64\" : \"eyAieHl6IjogMTIzIH0=\"\n" +
|
||||||
|
"}";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void impliedDataContentTypeNonBinaryData() throws IOException {
|
||||||
|
ObjectMapper mapper = getObjectMapper(false);
|
||||||
|
StringReader reader = new StringReader(nonBinaryPayload);
|
||||||
|
CloudEvent ce = mapper.readValue(reader, CloudEvent.class);
|
||||||
|
assertThat(ce.getDataContentType()).isEqualTo("application/json");
|
||||||
|
|
||||||
|
mapper = getObjectMapper(true);
|
||||||
|
reader = new StringReader(nonBinaryPayload);
|
||||||
|
ce = mapper.readValue(reader, CloudEvent.class);
|
||||||
|
assertThat(ce.getDataContentType()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void impliedDataContentTypeBinaryData() throws IOException {
|
||||||
|
final ObjectMapper mapper = getObjectMapper(false);
|
||||||
|
StringReader reader = new StringReader(binaryPayload);
|
||||||
|
CloudEvent ce = mapper.readValue(reader, CloudEvent.class);
|
||||||
|
assertThat(ce.getDataContentType()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectMapper getObjectMapper(boolean disableDataContentTypeDefaulting) {
|
||||||
|
final ObjectMapper mapper = new ObjectMapper();
|
||||||
|
final SimpleModule module = getCloudEventJacksonModule(
|
||||||
|
JsonFormatOptions
|
||||||
|
.builder()
|
||||||
|
.disableDataContentTypeDefaulting(disableDataContentTypeDefaulting)
|
||||||
|
.build()
|
||||||
|
);
|
||||||
|
mapper.registerModule(module);
|
||||||
|
return mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue