Compare commits
137 Commits
Author | SHA1 | Date |
---|---|---|
|
0d96691f6d | |
|
c8a22b5175 | |
|
57f9867a5c | |
|
43f0d5b138 | |
|
efe7e01b75 | |
|
188c3c7085 | |
|
659bb8cc01 | |
|
9981635e7d | |
|
b54df5ca0c | |
|
a3decf768c | |
|
215e67f2c3 | |
|
514e00d75b | |
|
f7ac215bf1 | |
|
5f4abd144a | |
|
8d59d29bb1 | |
|
6ab42de007 | |
|
2e87988d0c | |
|
6d958b69d4 | |
|
1de03ad38a | |
|
4c5d0efb89 | |
|
01a9111d8b | |
|
e056d1b2d8 | |
|
efdf0ba866 | |
|
bd90d903ce | |
|
52a98d778c | |
|
010627e784 | |
|
a7904823c3 | |
|
e5a35ac472 | |
|
4d204cef89 | |
|
43afb6ceaa | |
|
9c6e7fd11e | |
|
98deac1599 | |
|
05baf9be8a | |
|
3add823e00 | |
|
5a03173dde | |
|
9ee16fb48c | |
|
0db4446163 | |
|
111fb55cfd | |
|
55fddb35fc | |
|
7b9d020acc | |
|
fb11b94f2b | |
|
eaef3becdd | |
|
b30324e916 | |
|
1f9fa13231 | |
|
a135755ec6 | |
|
677a2c2628 | |
|
826e099fc0 | |
|
76366338fc | |
|
4ef304115a | |
|
582feed520 | |
|
5ef1088a19 | |
|
698cdf7ad4 | |
|
4ebeab0e0f | |
|
4c81f3eacc | |
|
569e025cf0 | |
|
3614a4f5f4 | |
|
d64aff7327 | |
|
d1cff75230 | |
|
1591cb337a | |
|
f71303b7b7 | |
|
d59b33307a | |
|
e0d1961f35 | |
|
7c6b52ab30 | |
|
433ec5b274 | |
|
40fe91a5e0 | |
|
a43f90f4e2 | |
|
e488269510 | |
|
7d50c7fc7a | |
|
f1a86af656 | |
|
354f7a16ef | |
|
9132a13d81 | |
|
a491c85eb2 | |
|
6362bfbcd6 | |
|
f08a099ed9 | |
|
4139fb7e57 | |
|
adde53c817 | |
|
0dc10251ff | |
|
b9eaa2fcaa | |
|
f8d27b08bf | |
|
9125136530 | |
|
f35e6e610a | |
|
45ec85f8c1 | |
|
1d87fb7191 | |
|
8f9b741306 | |
|
2dd8ba95dd | |
|
1c29726e8c | |
|
6681205733 | |
|
a4bc7a8368 | |
|
4784f03e8c | |
|
ace6859ae0 | |
|
cc786251d5 | |
|
ceb06757a3 | |
|
8d91cdaee6 | |
|
d00ad967c0 | |
|
9231e6d230 | |
|
a94bc5c81c | |
|
32adfe9123 | |
|
0277ee4ae4 | |
|
202849307c | |
|
624ac693d8 | |
|
5a323942d3 | |
|
1587708805 | |
|
06c4ec5385 | |
|
73a3c370d5 | |
|
722f5205b3 | |
|
8ad857d8c7 | |
|
baa9b5927a | |
|
c41a2c3ba7 | |
|
3651cdae18 | |
|
ee4c85b1a1 | |
|
a0b0835180 | |
|
687e03bac5 | |
|
208b18c299 | |
|
9d45943844 | |
|
0a1a03db64 | |
|
7f355d10c1 | |
|
3234e30e55 | |
|
c8f10e9215 | |
|
ba9ccad5d2 | |
|
78355bb225 | |
|
2730ae4a13 | |
|
30fd6769eb | |
|
ff07dd8315 | |
|
928ebcfd6f | |
|
a4613c00d2 | |
|
69f0e20549 | |
|
e2b13109e4 | |
|
5e3bfc890f | |
|
13f8b56618 | |
|
a419d8bba3 | |
|
47bed5616d | |
|
baba37ccfd | |
|
32bcdcd3b9 | |
|
8b382734d9 | |
|
f05418cba9 | |
|
23cd08fcfd | |
|
70782da2c2 |
|
@ -0,0 +1,11 @@
|
|||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "maven"
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
|
@ -4,6 +4,8 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- '[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
|
@ -11,7 +13,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 8, 11 ]
|
||||
java: [ 8, 11, 17 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
|
@ -19,8 +21,9 @@ jobs:
|
|||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- run: |
|
||||
mvn clean install -DskipTests -B
|
||||
mvn verify -B
|
||||
./mvnw clean install -DskipTests -B
|
||||
./mvnw verify -B
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
|
@ -31,13 +34,13 @@ jobs:
|
|||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-id: central # Value of the distributionManagement/repository/id field of the pom.xml
|
||||
server-username: MAVEN_USERNAME # env variable for username in deploy
|
||||
server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
|
||||
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
|
||||
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
|
||||
- name: Publish to Apache Maven Central
|
||||
run: mvn clean deploy -Drelease -DskipTests -B
|
||||
run: ./mvnw clean deploy -Drelease -DskipTests -B
|
||||
env:
|
||||
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
|
|
@ -5,6 +5,14 @@ on:
|
|||
version:
|
||||
description: 'Version to bump (without prepending "v")'
|
||||
required: true
|
||||
maven-modules:
|
||||
description: "Whether to bump versions in pom.xml files"
|
||||
type: choice
|
||||
required: true
|
||||
default: 'true'
|
||||
options:
|
||||
- 'true'
|
||||
- 'false'
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
|
@ -19,7 +27,8 @@ jobs:
|
|||
with:
|
||||
java-version: 8
|
||||
- name: Bump version using Maven
|
||||
run: 'mvn versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -B'
|
||||
if: ${{ inputs.maven-modules == 'true' }}
|
||||
run: './mvnw versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -B'
|
||||
- name: Bump version in docs
|
||||
if: ${{ !endsWith(github.event.inputs.version, 'SNAPSHOT') }}
|
||||
run: 'find . -type f -name "*.md" -exec sed -i -e "s+<version>[a-zA-Z0-9.-]*<\/version>+<version>$NEW_VERSION</version>+g" {} +'
|
||||
|
|
|
@ -4,20 +4,24 @@ on:
|
|||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- '[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 8, 11 ]
|
||||
java: [ 8, 11, 17 ]
|
||||
name: Java ${{ matrix.java }} Test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
- run: |
|
||||
mvn clean install -DskipTests -B
|
||||
mvn verify -B
|
||||
./mvnw clean install -DskipTests -B
|
||||
./mvnw verify -B
|
||||
./mvnw javadoc:javadoc
|
||||
|
|
|
@ -45,3 +45,5 @@ _site/
|
|||
|
||||
# MacOS
|
||||
*.DS_Store
|
||||
/http/restful-ws-jakarta/src/main/*
|
||||
|
||||
|
|
|
@ -1,4 +1,19 @@
|
|||
# Pull Request Guidelines
|
||||
# Contributing to CloudEvents' Java SDK
|
||||
|
||||
:+1::tada: First off, thanks for taking the time to contribute! :tada::+1:
|
||||
|
||||
We welcome contributions from the community! Please take some time to become
|
||||
acquainted with the process before submitting a pull request. There are just
|
||||
a few things to keep in mind.
|
||||
|
||||
# Pull Requests
|
||||
|
||||
Typically, a pull request should relate to an existing issue. If you have
|
||||
found a bug, want to add an improvement, or suggest an API change, please
|
||||
create an issue before proceeding with a pull request. For very minor changes
|
||||
such as typos in the documentation this isn't really necessary.
|
||||
|
||||
## Pull Request Guidelines
|
||||
|
||||
Here you will find step by step guidance for creating, submitting and updating
|
||||
a pull request in this repository. We hope it will help you have an easy time
|
||||
|
@ -8,7 +23,7 @@ your code. Thanks for getting involved! :rocket:
|
|||
* [Getting Started](#getting-started)
|
||||
* [Branches](#branches)
|
||||
* [Commit Messages](#commit-messages)
|
||||
* [Staying current with master](#staying-current-with-master)
|
||||
* [Staying current with main](#staying-current-with-main)
|
||||
* [Submitting and Updating a Pull Request](#submitting-and-updating-a-pull-request)
|
||||
* [Congratulations!](#congratulations)
|
||||
|
||||
|
@ -32,7 +47,7 @@ organized.
|
|||
|
||||
```console
|
||||
git fetch upstream
|
||||
git reset --hard upstream/master
|
||||
git reset --hard upstream/main
|
||||
git checkout FETCH_HEAD
|
||||
git checkout -b 48-fix-http-agent-error
|
||||
```
|
||||
|
@ -87,19 +102,19 @@ Date: Thu Feb 2 11:41:15 2018 -0800
|
|||
Notice the `Author` and `Signed-off-by` lines match. If they don't your PR will
|
||||
be rejected by the automated DCO check.
|
||||
|
||||
## Staying Current with `master`
|
||||
## Staying Current with `main`
|
||||
|
||||
As you are working on your branch, changes may happen on `master`. Before
|
||||
As you are working on your branch, changes may happen on `main`. Before
|
||||
submitting your pull request, be sure that your branch has been updated
|
||||
with the latest commits.
|
||||
|
||||
```console
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
git rebase upstream/main
|
||||
```
|
||||
|
||||
This may cause conflicts if the files you are changing on your branch are
|
||||
also changed on master. Error messages from `git` will indicate if conflicts
|
||||
also changed on main. Error messages from `git` will indicate if conflicts
|
||||
exist and what files need attention. Resolve the conflicts in each file, then
|
||||
continue with the rebase with `git rebase --continue`.
|
||||
|
||||
|
@ -116,15 +131,15 @@ git push -f origin 48-fix-http-agent-error
|
|||
Before submitting a pull request, you should make sure that all of the tests
|
||||
successfully pass.
|
||||
|
||||
Once you have sent your pull request, `master` may continue to evolve
|
||||
before your pull request has landed. If there are any commits on `master`
|
||||
Once you have sent your pull request, `main` may continue to evolve
|
||||
before your pull request has landed. If there are any commits on `main`
|
||||
that conflict with your changes, you may need to update your branch with
|
||||
these changes before the pull request can land. Resolve conflicts the same
|
||||
way as before.
|
||||
|
||||
```console
|
||||
git fetch upstream
|
||||
git rebase upstream/master
|
||||
git rebase upstream/main
|
||||
# fix any potential conflicts
|
||||
git push -f origin 48-fix-http-agent-error
|
||||
```
|
||||
|
@ -141,7 +156,7 @@ for details.
|
|||
|
||||
```console
|
||||
git commit -m "fixup: fix typo"
|
||||
git rebase -i upstream/master # follow git instructions
|
||||
git rebase -i upstream/main # follow git instructions
|
||||
```
|
||||
|
||||
Once you have rebased your commits, you can force push to your fork as before.
|
|
@ -0,0 +1,5 @@
|
|||
# Maintainers
|
||||
|
||||
Current active maintainers of this SDK:
|
||||
|
||||
- [Pierangelo Di Pilato](https://github.com/pierDipi)
|
|
@ -1,17 +1,5 @@
|
|||
# Maintainer's Guide
|
||||
|
||||
## Release Process
|
||||
|
||||
The release process is automated with Github actions. In order to perform a release:
|
||||
|
||||
1. Check if master CI pass.
|
||||
1. Open the Github repository main page and go in the tab "Actions". Trigger the workflow "Bump version" and insert the new version to release. This will create a new release PR.
|
||||
1. Check the release PR, merge it and cleanup the created branch.
|
||||
1. Wait for the CI to complete the deploy of the modules to OSSRH.
|
||||
1. Using the Github UI, create a new release, specifying the release notes and the tag to use.
|
||||
1. Trigger again the workflow "Bump version" to bump versions back to a snapshot version.
|
||||
1. Check the snapshot release PR, merge it and cleanup the created branch.
|
||||
|
||||
## Tips
|
||||
|
||||
Here are a few tips for repository maintainers.
|
||||
|
@ -38,14 +26,14 @@ When landing pull requests, be sure to check the first line uses an appropriate
|
|||
|
||||
## Branch Management
|
||||
|
||||
The `master` branch is the bleeding edge. New major versions of the module
|
||||
The `main` branch is the bleeding edge. New major versions of the module
|
||||
are cut from this branch and tagged. If you intend to submit a pull request
|
||||
you should use `master HEAD` as your starting point.
|
||||
you should use `main HEAD` as your starting point.
|
||||
|
||||
Each major release will result in a new branch and tag. For example, the
|
||||
release of version 1.0.0 of the module will result in a `v1.0.0` tag on the
|
||||
release commit, and a new branch `v1.x.y` for subsequent minor and patch
|
||||
level releases of that major version. However, development will continue
|
||||
apace on `master` for the next major version - e.g. 2.0.0. Version branches
|
||||
apace on `main` for the next major version - e.g. 2.0.0. Version branches
|
||||
are only created for each major version. Minor and patch level releases
|
||||
are simply tagged.
|
36
README.md
36
README.md
|
@ -43,13 +43,18 @@ Supported features of the specification:
|
|||
| - [Jakarta Restful WS](http/restful-ws) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Basic](http/basic) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Spring](spring) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [http4k][http4k]<sup>†</sup>| :heavy_check_mark: | :heavy_check_mark: |
|
||||
| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jackson](formats/json-jackson) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Proto](formats/protobuf) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| [Kafka Protocol Binding](kafka) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
|
||||
<sub>† Source/artifacts hosted externally</sub>
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation is available at https://cloudevents.github.io/sdk-java/.
|
||||
|
@ -58,7 +63,10 @@ Javadocs are available on [javadoc.io](https://www.javadoc.io):
|
|||
|
||||
- [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
- [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
- [cloudevents-avro-compact](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact)
|
||||
- [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
|
||||
- [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
|
||||
- [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml)
|
||||
- [cloudevents-http-basic](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-basic)
|
||||
- [cloudevents-http-restful-ws](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||
- [cloudevents-http-vertx](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-vertx)
|
||||
|
@ -70,9 +78,9 @@ You can check out the examples in the [examples](examples) directory.
|
|||
|
||||
## Used By
|
||||
|
||||
| [Occurrent](https://occurrent.org) | [Knative Eventing](https://github.com/knative-sandbox/eventing-kafka-broker) |
|
||||
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <a href="https://occurrent.org"><img src="https://raw.githubusercontent.com/johanhaleby/occurrent/master/occurrent-logo-196x196.png" width="98" height="98" alt="Occurrent" title="Occurrent - Event Sourcing Utilities for the JVM"></img></a> | <a href="https://github.com/knative-sandbox/eventing-kafka-broker"><img src="https://cloudevents.io/img/logos/integrations/knative.png" height="98"></img></a> |
|
||||
| [Occurrent](https://occurrent.org) | [Knative Eventing](https://github.com/knative-sandbox/eventing-kafka-broker )| [http4k][http4k] |
|
||||
| ---------------------------------- | ---------------------------------------------------------------------------- | ---------------|
|
||||
| <a href="https://occurrent.org"><img src="https://raw.githubusercontent.com/johanhaleby/occurrent/master/occurrent-logo-196x196.png" width="98" height="98" alt="Occurrent" title="Occurrent - Event Sourcing Utilities for the JVM"></img></a> | <a href="https://github.com/knative-sandbox/eventing-kafka-broker"><img src="https://cloudevents.io/img/logos/integrations/knative.png" height="98"></img></a> | <a href="https://www.http4k.org/guide/modules/cloud_events/"><img src="https://http4k.org/img/favicon-310.png" height="98" alt="http4k" title="http4k"></img></a> | |
|
||||
|
||||
## Community
|
||||
|
||||
|
@ -91,11 +99,25 @@ You can check out the examples in the [examples](examples) directory.
|
|||
|
||||
Each SDK may have its own unique processes, tooling and guidelines, common
|
||||
governance related material can be found in the
|
||||
[CloudEvents `community`](https://github.com/cloudevents/spec/tree/master/community)
|
||||
[CloudEvents `community`](https://github.com/cloudevents/spec/tree/main/docs)
|
||||
directory. In particular, in there you will find information concerning how SDK
|
||||
projects are
|
||||
[managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md),
|
||||
[guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md)
|
||||
[managed](https://github.com/cloudevents/spec/blob/main/docs/SDK-GOVERNANCE.md),
|
||||
[guidelines](https://github.com/cloudevents/spec/blob/main/docs/SDK-maintainer-guidelines.md)
|
||||
for how PR reviews and approval, and our
|
||||
[Code of Conduct](https://github.com/cloudevents/spec/blob/master/community/GOVERNANCE.md#additional-information)
|
||||
[Code of Conduct](https://github.com/cloudevents/spec/blob/main/docs/GOVERNANCE.md#additional-information)
|
||||
information.
|
||||
|
||||
If there is a security concern with one of the CloudEvents specifications, or
|
||||
with one of the project's SDKs, please send an email to
|
||||
[cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io).
|
||||
|
||||
## Additional SDK Resources
|
||||
|
||||
- [List of current active maintainers](MAINTAINERS.md)
|
||||
- [How to contribute to the project](CONTRIBUTING.md)
|
||||
- [SDK's License](LICENSE)
|
||||
- [SDK's Release process](RELEASING.md)
|
||||
- [SDK Maintainer's guide](MAINTAINERS_GUIDE.md)
|
||||
|
||||
[http4k]: https://www.http4k.org/guide/reference/cloud_events/
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# Release Process
|
||||
|
||||
The release process is automated with Github actions. In order to perform a release:
|
||||
|
||||
1. Check if main CI pass.
|
||||
1. Open the Github repository main page and go in the tab "Actions". Trigger the workflow "Bump version" and insert the new version to release. This will create a new release PR.
|
||||
1. Check the release PR, merge it and cleanup the created branch.
|
||||
1. Wait for the CI to complete the deploy of the modules to OSSRH.
|
||||
1. Using the Github UI, create a new release, specifying the release notes and the tag to use.
|
||||
1. Trigger again the workflow "Bump version" to bump versions back to a snapshot version.
|
||||
1. Check the snapshot release PR, merge it and cleanup the created branch.
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
|
@ -14,7 +14,7 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<protonj.version>0.33.7</protonj.version>
|
||||
<protonj.version>0.34.1</protonj.version>
|
||||
<jsr305.version>3.0.2</jsr305.version>
|
||||
<module-name>io.cloudevents.amqp.proton</module-name>
|
||||
</properties>
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
|
|
|
@ -39,7 +39,7 @@ public interface CloudEventExtension {
|
|||
* Get the attribute of extension named {@code key}.
|
||||
*
|
||||
* @param key the name of the extension attribute
|
||||
* @return the extension value in one of the valid types String/Number/Boolean
|
||||
* @return the extension value in one of the valid types String/Number/Boolean/URI/OffsetDateTime/byte[]
|
||||
* @throws IllegalArgumentException if the key is unknown to this extension
|
||||
*/
|
||||
@Nullable
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.cloudevents.types.Time;
|
|||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Interface to write the context attributes/extensions from a {@link io.cloudevents.rw.CloudEventContextReader} to a new representation.
|
||||
|
@ -73,7 +74,25 @@ public interface CloudEventContextWriter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
* Set attribute with type {@link Number}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*
|
||||
* @deprecated CloudEvent specification only permits {@link Integer} type as a
|
||||
* numeric value.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Integer}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
|
@ -83,7 +102,7 @@ public interface CloudEventContextWriter {
|
|||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
default CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
|
@ -102,4 +121,18 @@ public interface CloudEventContextWriter {
|
|||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with a binary type.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, byte[] value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, Base64.getEncoder().encodeToString(value));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,6 +137,17 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newInvalidAttributeType(String attributeName,Object value){
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
"Invalid attribute/extension type for \""
|
||||
+ attributeName
|
||||
+ "\": Type" + value.getClass().getCanonicalName()
|
||||
+ ". Value: " + value
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @param value the value of the attribute
|
||||
|
@ -205,4 +216,15 @@ public class CloudEventRWException extends RuntimeException {
|
|||
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}.
|
||||
*
|
||||
* @param <W> The type of the {@link CloudEventWriter} created by writerFactory
|
||||
* @param <R> The return value of the {@link CloudEventWriter} created by writerFactory
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @see #read(CloudEventWriterFactory, CloudEventDataMapper)
|
||||
* @return the value returned by {@link CloudEventWriter#end()} or {@link CloudEventWriter#end(CloudEventData)}
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
*/
|
||||
default <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException {
|
||||
return read(writerFactory, CloudEventDataMapper.identity());
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-benchmarks</artifactId>
|
||||
|
@ -59,6 +59,11 @@
|
|||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-sql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
|
@ -136,7 +141,7 @@
|
|||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>2.5.1</version>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -156,7 +161,7 @@
|
|||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>2.2.1</version>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package io.cloudevents.bench.sql;
|
||||
|
||||
import io.cloudevents.sql.Parser;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
public class CompileBenchmark {
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(a + b + c) = 10 AND TRUE OR CONCAT('1', '2', id) = '123'")
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallTrueConstantExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(1 + 2 + 3) = 10 AND TRUE OR CONCAT('1', '2', '3') = 123")
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallFalseConstantExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(1 + 2 + 3) = 10 AND FALSE OR CONCAT('1', '2', '3') = 124")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.cloudevents.bench.sql;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.sql.Expression;
|
||||
import io.cloudevents.sql.Parser;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_JSON_DATA_WITH_EXT;
|
||||
|
||||
public class RunBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("a", "10")
|
||||
.withExtension("b", 3)
|
||||
.withExtension("c", "-3")
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(a + b + c) = 10 AND TRUE OR CONCAT('1', '2', id) = '123'");
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallTrueConstantExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(1 + 2 + 3) = 10 AND TRUE OR CONCAT('1', '2', '3') = 123");
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallFalseConstantExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(1 + 2 + 3) = 10 AND FALSE OR CONCAT('1', '2', '3') = 124");
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallExpression(TestCaseSmallExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallExpression(TestCaseSmallExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallTrueConstantExpression(TestCaseSmallTrueConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallTrueConstantExpression(TestCaseSmallTrueConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallFalseConstantExpression(TestCaseSmallFalseConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallFalseConstantExpression(TestCaseSmallFalseConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2018-Present The CloudEvents Authors
|
||||
~ <p>
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~ <p>
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~ <p>
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
~
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-bom</artifactId>
|
||||
<name>CloudEvents - Bill of Materials</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-json-jackson</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-protobuf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-basic</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-vertx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-spring</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-sql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
|
@ -22,7 +22,7 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
|
|
|
@ -141,6 +141,28 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
*/
|
||||
CloudEventBuilder withData(String dataContentType, URI dataSchema, CloudEventData data);
|
||||
|
||||
/**
|
||||
* Remove the {@code datacontenttype}, {@code dataschema} and {@code data} from the event
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutData();
|
||||
|
||||
/**
|
||||
* Remove the {@code dataschema} from the event
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutDataSchema();
|
||||
|
||||
|
||||
/**
|
||||
* Remove the {@code datacontenttype} from the event
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutDataContentType();
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and string value
|
||||
*
|
||||
|
@ -186,6 +208,15 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull OffsetDateTime value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and binary value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull byte[] value);
|
||||
|
||||
/**
|
||||
* Add to the builder all the extension key/values of the provided extension
|
||||
*
|
||||
|
@ -300,9 +331,9 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
static CloudEventBuilder fromContext(@Nonnull CloudEventContext context) {
|
||||
switch (context.getSpecVersion()) {
|
||||
case V1:
|
||||
return new io.cloudevents.core.v1.CloudEventBuilder(context);
|
||||
return new io.cloudevents.core.v1.CloudEventBuilder(context);
|
||||
case V03:
|
||||
return new io.cloudevents.core.v03.CloudEventBuilder(context);
|
||||
return new io.cloudevents.core.v03.CloudEventBuilder(context);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"The provided spec version doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
* @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 {
|
||||
|
||||
|
|
|
@ -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(); }
|
||||
|
||||
}
|
|
@ -77,6 +77,8 @@ public abstract class BaseCloudEvent implements CloudEvent, CloudEventReader, Cl
|
|||
writer.withContextAttribute(entry.getKey(), (URI) entry.getValue());
|
||||
} else if (entry.getValue() instanceof OffsetDateTime) {
|
||||
writer.withContextAttribute(entry.getKey(), (OffsetDateTime) entry.getValue());
|
||||
} else if (entry.getValue() instanceof byte[]) {
|
||||
writer.withContextAttribute(entry.getKey(), (byte[]) entry.getValue());
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
|
||||
|
|
|
@ -98,6 +98,24 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withoutData() {
|
||||
this.data = null;
|
||||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withoutDataSchema() {
|
||||
withDataSchema(null);
|
||||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withoutDataContentType() {
|
||||
withDataContentType(null);
|
||||
return this.self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull String value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
|
@ -106,6 +124,9 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return self;
|
||||
}
|
||||
|
||||
// @TODO - I think this method should be removed/deprecated
|
||||
// **Number** Is NOT a valid CE Context atrribute type.
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Number value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
|
@ -114,6 +135,14 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Integer value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Boolean value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
|
@ -140,6 +169,15 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withExtension(@Nonnull String key, @Nonnull byte[] value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withoutExtension(@Nonnull String key) {
|
||||
this.extensions.remove(key);
|
||||
|
@ -180,22 +218,24 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
protected static IllegalStateException createMissingAttributeException(String attributeName) {
|
||||
return new IllegalStateException("Attribute '" + attributeName + "' cannot be null");
|
||||
}
|
||||
|
||||
protected static IllegalStateException createEmptyAttributeException(String attributeName) {
|
||||
return new IllegalStateException("Attribute '" + attributeName + "' cannot be empty");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the extension name as defined in CloudEvents spec.
|
||||
*
|
||||
* @param name the extension name
|
||||
* @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) {
|
||||
if(name.length() > 20){
|
||||
return false;
|
||||
}
|
||||
char[] chars = name.toCharArray();
|
||||
for (char c : chars)
|
||||
if (!isValidChar(c)) {
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
if (!isValidChar(name.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
// in order to complete the visit in one loop
|
||||
this.forEachHeader((key, value) -> {
|
||||
if (value == null) {
|
||||
return;
|
||||
}
|
||||
if (isContentTypeHeader(key)) {
|
||||
visitor.withContextAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
|
||||
} else if (isCloudEventsHeader(key)) {
|
||||
|
|
|
@ -59,6 +59,15 @@ public class MessageUtils {
|
|||
EventFormat format = EventFormatProvider.getInstance().resolveFormat(ct);
|
||||
if (format != null) {
|
||||
return structuredMessageFactory.apply(format);
|
||||
} else {
|
||||
/**
|
||||
* The format wasn't one we support, but if it's part of the
|
||||
* CloudEvent family it indicates it's a structured
|
||||
* representation that we can't interpret.
|
||||
*/
|
||||
if (ct.startsWith("application/cloudevents")) {
|
||||
throw newUnknownEncodingException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package io.cloudevents.core.provider;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* CloudEventValidatorProvider is a singleton class which loads and access CE Validator service providers on behalf of service clients.
|
||||
*/
|
||||
public class CloudEventValidatorProvider {
|
||||
|
||||
private static final CloudEventValidatorProvider cloudEventValidatorProvider = new CloudEventValidatorProvider();
|
||||
|
||||
private final Collection<CloudEventValidator> validators;
|
||||
|
||||
private CloudEventValidatorProvider() {
|
||||
final ServiceLoader<CloudEventValidator> loader = ServiceLoader.load(CloudEventValidator.class);
|
||||
this.validators = new ArrayList<>(2);
|
||||
for (CloudEventValidator cloudEventValidator : loader) {
|
||||
validators.add(cloudEventValidator);
|
||||
}
|
||||
}
|
||||
|
||||
public static CloudEventValidatorProvider getInstance() {
|
||||
return cloudEventValidatorProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* iterates through available Cloudevent validators.
|
||||
*
|
||||
* @param cloudEvent event to validate.
|
||||
*/
|
||||
public void validate(CloudEvent cloudEvent) {
|
||||
for (final CloudEventValidator validator : validators) {
|
||||
validator.validate(cloudEvent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,70 +17,96 @@
|
|||
|
||||
package io.cloudevents.core.provider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import io.cloudevents.core.format.ContentType;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.HashMap;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Singleton holding the discovered {@link EventFormat} implementations through {@link ServiceLoader}.
|
||||
* Singleton holding the discovered {@link EventFormat} implementations through
|
||||
* {@link ServiceLoader}.
|
||||
* <p>
|
||||
* You can resolve an event format using {@code EventFormatProvider.getInstance().resolveFormat(contentType)}.
|
||||
* You can resolve an event format using
|
||||
* {@code EventFormatProvider.getInstance().resolveFormat(contentType)}.
|
||||
* <p>
|
||||
* You can programmatically add a new {@link EventFormat} implementation using {@link #registerFormat(EventFormat)}.
|
||||
* You can programmatically add a new {@link EventFormat} implementation using
|
||||
* {@link #registerFormat(EventFormat)}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class EventFormatProvider {
|
||||
|
||||
private static class SingletonContainer {
|
||||
private final static EventFormatProvider INSTANCE = new EventFormatProvider();
|
||||
}
|
||||
private static class SingletonContainer {
|
||||
private final static EventFormatProvider INSTANCE = new EventFormatProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return instance of {@link EventFormatProvider}
|
||||
*/
|
||||
public static EventFormatProvider getInstance() {
|
||||
return EventFormatProvider.SingletonContainer.INSTANCE;
|
||||
}
|
||||
/**
|
||||
* @return instance of {@link EventFormatProvider}
|
||||
*/
|
||||
public static EventFormatProvider getInstance() {
|
||||
return EventFormatProvider.SingletonContainer.INSTANCE;
|
||||
}
|
||||
|
||||
private final HashMap<String, EventFormat> formats;
|
||||
private final HashMap<String, EventFormat> formats;
|
||||
|
||||
private EventFormatProvider() {
|
||||
this.formats = new HashMap<>();
|
||||
private EventFormatProvider() {
|
||||
this.formats = new HashMap<>();
|
||||
|
||||
StreamSupport.stream(
|
||||
ServiceLoader.load(EventFormat.class).spliterator(),
|
||||
false
|
||||
).forEach(this::registerFormat);
|
||||
}
|
||||
StreamSupport.stream(ServiceLoader.load(EventFormat.class).spliterator(), false)
|
||||
.forEach(this::registerFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new {@link EventFormat} programmatically.
|
||||
*
|
||||
* @param format the new format to register
|
||||
*/
|
||||
public void registerFormat(EventFormat format) {
|
||||
for (String k : format.deserializableContentTypes()) {
|
||||
this.formats.put(k, format);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register a new {@link EventFormat} programmatically.
|
||||
*
|
||||
* @param format the new format to register
|
||||
*/
|
||||
public void registerFormat(EventFormat format) {
|
||||
for (String k : format.deserializableContentTypes()) {
|
||||
this.formats.put(k, format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(String contentType) {
|
||||
int i = contentType.indexOf(';');
|
||||
if (i != -1) {
|
||||
contentType = contentType.substring(0, i);
|
||||
}
|
||||
return this.formats.get(contentType);
|
||||
}
|
||||
/**
|
||||
* Enumerate the supported content types.
|
||||
*
|
||||
* @return an alphabetically sorted list of content types
|
||||
*/
|
||||
public Set<String> getContentTypes() {
|
||||
Set<String> types = new TreeSet<>();
|
||||
types.addAll(this.formats.keySet());
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(String contentType) {
|
||||
int i = contentType.indexOf(';');
|
||||
if (i != -1) {
|
||||
contentType = contentType.substring(0, i);
|
||||
}
|
||||
return this.formats.get(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(ContentType contentType) {
|
||||
return this.formats.get(contentType.value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,9 +16,12 @@
|
|||
*/
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.core.provider.CloudEventValidatorProvider;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
@ -121,8 +124,15 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
if (type == null) {
|
||||
throw createMissingAttributeException("type");
|
||||
}
|
||||
if (subject != null && subject.isEmpty()) {
|
||||
throw createEmptyAttributeException(("subject"));
|
||||
}
|
||||
|
||||
return new CloudEventV03(id, source, type, time, schemaurl, datacontenttype, subject, this.data, this.extensions);
|
||||
CloudEventV03 cloudEvent = new CloudEventV03(id, source, type, time, schemaurl, datacontenttype, subject, this.data, this.extensions);
|
||||
final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance();
|
||||
validator.validate(cloudEvent);
|
||||
|
||||
return cloudEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -246,6 +256,26 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
|
||||
{
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
|
@ -264,4 +294,23 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, byte[] value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, byte[].class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -236,12 +236,12 @@ public final class CloudEventV03 extends BaseCloudEvent {
|
|||
"id='" + id + '\'' +
|
||||
", source=" + source +
|
||||
", type='" + type + '\'' +
|
||||
", datacontenttype='" + datacontenttype + '\'' +
|
||||
", schemaurl=" + schemaurl +
|
||||
", subject='" + subject + '\'' +
|
||||
", time=" + time +
|
||||
", data=" + getData() +
|
||||
", extensions" + this.extensions +
|
||||
((datacontenttype != null) ? ", datacontenttype='" + datacontenttype + '\'' : "") +
|
||||
((schemaurl != null) ? ", schemaurl=" + schemaurl : "") +
|
||||
((subject != null) ? ", subject='" + subject + '\'' : "") +
|
||||
((time != null) ? ", time=" + time : "") +
|
||||
((getData() != null) ? ", data=" + getData() : "") +
|
||||
", extensions=" + this.extensions +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import io.cloudevents.CloudEvent;
|
|||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.core.provider.CloudEventValidatorProvider;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
@ -118,8 +120,16 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
if (type == null) {
|
||||
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
|
||||
|
@ -238,6 +248,25 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
|
||||
{
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
|
@ -255,4 +284,23 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, byte[] value)
|
||||
throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, byte[].class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -223,11 +223,11 @@ public final class CloudEventV1 extends BaseCloudEvent {
|
|||
"id='" + id + '\'' +
|
||||
", source=" + source +
|
||||
", type='" + type + '\'' +
|
||||
", datacontenttype='" + datacontenttype + '\'' +
|
||||
", dataschema=" + dataschema +
|
||||
", subject='" + subject + '\'' +
|
||||
", time=" + time +
|
||||
", data=" + getData() +
|
||||
((datacontenttype != null) ? ", datacontenttype='" + datacontenttype + '\'' : "") +
|
||||
((dataschema != null) ? ", dataschema=" + dataschema : "") +
|
||||
((subject != null) ? ", subject='" + subject + '\'' : "") +
|
||||
((time != null) ? ", time=" + time : "") +
|
||||
((getData() != null) ? ", data=" + getData() : "") +
|
||||
", extensions=" + this.extensions +
|
||||
'}';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
@ -8,7 +10,7 @@ class PojoCloudEventDataTest {
|
|||
|
||||
@Test
|
||||
void testWrapAndMemoization() {
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, i -> i.toString().getBytes());
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, i -> i.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(data.getValue())
|
||||
.isEqualTo(10);
|
||||
|
@ -16,7 +18,7 @@ class PojoCloudEventDataTest {
|
|||
byte[] firstConversion = data.toBytes();
|
||||
|
||||
assertThat(firstConversion)
|
||||
.isEqualTo("10".getBytes());
|
||||
.isEqualTo("10".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(data.toBytes())
|
||||
.isSameAs(firstConversion);
|
||||
|
@ -24,7 +26,7 @@ class PojoCloudEventDataTest {
|
|||
|
||||
@Test
|
||||
void testAlreadySerializedValue() {
|
||||
byte[] serialized = "10".getBytes();
|
||||
byte[] serialized = "10".getBytes(StandardCharsets.UTF_8);
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, v -> serialized);
|
||||
|
||||
assertThat(data.getValue())
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.core.test.Data;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static io.cloudevents.core.test.Data.*;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
public class BaseCloudEventBuilderTest {
|
||||
|
||||
|
@ -49,17 +50,23 @@ public class BaseCloudEventBuilderTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testLongExtensionName() {
|
||||
Exception exception = assertThrows(RuntimeException.class, () -> {
|
||||
CloudEvent cloudEvent = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
public void testLongExtensionNameV1() {
|
||||
assertDoesNotThrow(() -> {
|
||||
CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("thisextensionnameistoolong", "")
|
||||
.build();
|
||||
});
|
||||
String expectedMessage = "Invalid extensions name: thisextensionnameistoolong";
|
||||
String actualMessage = exception.getMessage();
|
||||
|
||||
assertTrue(actualMessage.contains(expectedMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongExtensionNameV03() {
|
||||
assertDoesNotThrow(() -> {
|
||||
CloudEventBuilder.v03(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("thisextensionnameistoolong", "")
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidExtensionName() {
|
||||
Exception exception = assertThrows(RuntimeException.class, () -> {
|
||||
|
@ -72,4 +79,64 @@ public class BaseCloudEventBuilderTest {
|
|||
|
||||
assertTrue(actualMessage.contains(expectedMessage));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBinaryExtension() {
|
||||
|
||||
final String EXT_NAME = "verifyme";
|
||||
|
||||
CloudEvent given = CloudEventBuilder.v1(Data.V1_MIN)
|
||||
.withExtension(EXT_NAME, Data.BINARY_VALUE)
|
||||
.build();
|
||||
|
||||
// Sanity
|
||||
assertNotNull(given);
|
||||
|
||||
// Did the extension stick
|
||||
assertTrue(given.getExtensionNames().contains(EXT_NAME));
|
||||
assertNotNull(given.getExtension(EXT_NAME));
|
||||
|
||||
// Does the extension have the right value
|
||||
assertEquals(Data.BINARY_VALUE, given.getExtension(EXT_NAME));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutDataRemovesDataAttributeFromCopiedCloudEvent() {
|
||||
CloudEvent original = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
CloudEvent copy = CloudEventBuilder.v1(original).withoutData().build();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(copy.getData()).isNull(),
|
||||
() -> assertThat(copy.getDataContentType()).isEqualTo(DATACONTENTTYPE_JSON),
|
||||
() -> assertThat(copy.getDataSchema()).isEqualTo(DATASCHEMA)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutDataContentTypeRemovesDataContentTypeAttributeFromCopiedCloudEvent() {
|
||||
CloudEvent original = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
CloudEvent copy = CloudEventBuilder.v1(original).withoutDataContentType().build();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(Objects.requireNonNull(copy.getData()).toBytes()).isEqualTo(DATA_JSON_SERIALIZED),
|
||||
() -> assertThat(copy.getDataContentType()).isNull(),
|
||||
() -> assertThat(copy.getDataSchema()).isEqualTo(DATASCHEMA)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withoutDataSchemaRemovesDataSchemaAttributeFromCopiedCloudEvent() {
|
||||
CloudEvent original = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
CloudEvent copy = CloudEventBuilder.v1(original).withoutDataSchema().build();
|
||||
|
||||
assertAll(
|
||||
() -> assertThat(Objects.requireNonNull(copy.getData()).toBytes()).isEqualTo(DATA_JSON_SERIALIZED),
|
||||
() -> assertThat(copy.getDataContentType()).isEqualTo(DATACONTENTTYPE_JSON),
|
||||
() -> assertThat(copy.getDataSchema()).isNull()
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,4 +91,28 @@ public class CloudEventImplTest {
|
|||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringV1() {
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.build();
|
||||
|
||||
assertThat(event.toString())
|
||||
.doesNotContain("time");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testToStringV03() {
|
||||
CloudEvent event = CloudEventBuilder.v03()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.build();
|
||||
|
||||
assertThat(event.toString())
|
||||
.doesNotContain("time");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.impl;
|
||||
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class StringUtilsTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("startsWithIgnoreCaseArgs")
|
||||
public void startsWithIgnoreCase(final String s, final String prefix, final boolean expected) {
|
||||
Assertions.assertThat(StringUtils.startsWithIgnoreCase(s, prefix)).isEqualTo(expected);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> startsWithIgnoreCaseArgs() {
|
||||
return Stream.of(
|
||||
Arguments.of("s", "s", true),
|
||||
Arguments.of("sa", "S", true),
|
||||
Arguments.of("saS", "As", false),
|
||||
Arguments.of("sasso", "SASO", false),
|
||||
Arguments.of("sasso", "SaSsO", true)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -27,6 +27,25 @@ class MessageUtilsTest {
|
|||
.isEqualTo(CloudEventRWException.CloudEventRWExceptionKind.UNKNOWN_ENCODING);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify an exception is thrown if an unsupported
|
||||
* application/cloudevents content-type family is
|
||||
* received.
|
||||
*/
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
void testBadContentTypes(String contentType) {
|
||||
|
||||
CloudEventRWException exception = assertThrows(CloudEventRWException.class, () ->
|
||||
{
|
||||
parseStructuredOrBinaryMessage(() -> contentType, eventFormat -> null, () -> "1.0", specVersion -> null);
|
||||
});
|
||||
|
||||
assertThat(exception.getKind()).isEqualTo(CloudEventRWException.CloudEventRWExceptionKind.UNKNOWN_ENCODING);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseStructuredOrBinaryMessage_StructuredMode() {
|
||||
MessageUtils.parseStructuredOrBinaryMessage(() -> "application/cloudevents+csv;",
|
||||
|
@ -54,4 +73,11 @@ class MessageUtilsTest {
|
|||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> testBadContentTypes() {
|
||||
return Stream.of(
|
||||
Arguments.of("application/cloudevents"),
|
||||
Arguments.of("application/cloudevents+morse")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@ public class CSVFormat implements EventFormat {
|
|||
event.getData() != null
|
||||
? new String(Base64.getEncoder().encode(event.getData().toBytes()), StandardCharsets.UTF_8)
|
||||
: "null"
|
||||
).getBytes();
|
||||
).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -70,7 +70,7 @@ public class CSVFormat implements EventFormat {
|
|||
URI dataschema = splitted[5].equals("null") ? null : URI.create(splitted[5]);
|
||||
String subject = splitted[6].equals("null") ? null : splitted[6];
|
||||
OffsetDateTime time = splitted[7].equals("null") ? null : Time.parseTime(splitted[7]);
|
||||
byte[] data = splitted[8].equals("null") ? null : Base64.getDecoder().decode(splitted[8].getBytes());
|
||||
byte[] data = splitted[8].equals("null") ? null : Base64.getDecoder().decode(splitted[8].getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
CloudEventBuilder builder = CloudEventBuilder.fromSpecVersion(sv)
|
||||
.withId(id)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package io.cloudevents.core.mock;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
public class MyCloudEventData implements CloudEventData {
|
||||
|
@ -14,7 +14,7 @@ public class MyCloudEventData implements CloudEventData {
|
|||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return Integer.toString(value).getBytes();
|
||||
return Integer.toString(value).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
|
|
|
@ -36,4 +36,9 @@ public class EventFormatProviderTest {
|
|||
.isInstanceOf(CSVFormat.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listTypes() {
|
||||
assertThat(EventFormatProvider.getInstance().getContentTypes()).hasSize(1);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package io.cloudevents.core.test;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
|
||||
public class CloudEventCustomValidator implements CloudEventValidator {
|
||||
|
||||
@Override
|
||||
public void validate(CloudEvent cloudEvent) {
|
||||
String namespace = null;
|
||||
if ((namespace = (String) cloudEvent.getExtension("namespace")) != null &&
|
||||
!namespace.equals("sales")){
|
||||
throw new IllegalStateException("Expecting sales in namespace extension");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -21,7 +21,9 @@ import io.cloudevents.CloudEvent;
|
|||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -38,9 +40,10 @@ public class Data {
|
|||
public static final String SUBJECT = "sub";
|
||||
public static final OffsetDateTime TIME = Time.parseTime("2018-04-26T14:48:09+02:00");
|
||||
|
||||
public static byte[] DATA_JSON_SERIALIZED = "{}".getBytes();
|
||||
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes();
|
||||
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes();
|
||||
public static byte[] DATA_JSON_SERIALIZED = "{}".getBytes(StandardCharsets.UTF_8);
|
||||
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes(StandardCharsets.UTF_8);
|
||||
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes(StandardCharsets.UTF_8);
|
||||
public static byte[] BINARY_VALUE = { (byte) 0xE0, (byte) 0xFF, (byte) 0x00, (byte) 0x44, (byte) 0xAA }; // Base64: 4P8ARKo=
|
||||
|
||||
public static final CloudEvent V1_MIN = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
|
@ -108,6 +111,23 @@ public class Data {
|
|||
.withTime(TIME)
|
||||
.build();
|
||||
|
||||
public static final CloudEvent V1_WITH_BINARY_EXT = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.withExtension("binary", BINARY_VALUE)
|
||||
.build();
|
||||
|
||||
public static final CloudEvent V1_WITH_NUMERIC_EXT = CloudEventBuilder.v1()
|
||||
.withId(ID)
|
||||
.withType(TYPE)
|
||||
.withSource(SOURCE)
|
||||
.withExtension("integer", 42)
|
||||
.withExtension("decimal", new BigDecimal("42.42"))
|
||||
.withExtension("float", 4.2f)
|
||||
.withExtension("long", new Long(4200))
|
||||
.build();
|
||||
|
||||
public static final CloudEvent V03_MIN = CloudEventBuilder.v03(V1_MIN).build();
|
||||
public static final CloudEvent V03_WITH_JSON_DATA = CloudEventBuilder.v03(V1_WITH_JSON_DATA).build();
|
||||
public static final CloudEvent V03_WITH_JSON_DATA_WITH_EXT = CloudEventBuilder.v03(V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
|
@ -137,6 +157,18 @@ public class Data {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Due to the nature of CE there are scenarios where an event might be serialized
|
||||
* in such a fashion that it can not be deserialized while retaining the orginal
|
||||
* type information, this varies from format-2-format
|
||||
*/
|
||||
|
||||
public static Stream<CloudEvent> v1NonRoundTripEvents() {
|
||||
return Stream.of(
|
||||
Data.V1_WITH_BINARY_EXT
|
||||
);
|
||||
}
|
||||
|
||||
public static Stream<CloudEvent> v03Events() {
|
||||
return Stream.of(
|
||||
Data.V03_MIN,
|
||||
|
|
|
@ -158,4 +158,27 @@ public class CloudEventBuilderTest {
|
|||
).hasMessageContaining("Attribute 'type' cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingSubject() {
|
||||
CloudEvent actual = CloudEventBuilder
|
||||
.v03()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.build();
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptySubject() {
|
||||
assertThatCode(() -> CloudEventBuilder
|
||||
.v03()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.withSubject("")
|
||||
.build()
|
||||
).hasMessageContaining("Attribute 'subject' cannot be empty");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,4 +142,39 @@ public class CloudEventBuilderTest {
|
|||
).hasMessageContaining("Attribute 'type' cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testValidatorProvider(){
|
||||
assertThatCode(() -> CloudEventBuilder
|
||||
.v1()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.withExtension("namespace", "order")
|
||||
.build()
|
||||
).hasMessageContaining("Expecting sales in namespace extension");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testMissingSubject() {
|
||||
CloudEvent actual = CloudEventBuilder
|
||||
.v1()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.build();
|
||||
|
||||
assertThat(actual).isNotNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptySubject() {
|
||||
assertThatCode(() -> CloudEventBuilder
|
||||
.v1()
|
||||
.withId("000")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withType(TYPE)
|
||||
.withSubject("")
|
||||
.build()
|
||||
).hasMessageContaining("Attribute 'subject' cannot be empty");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
io.cloudevents.core.test.CloudEventCustomValidator
|
|
@ -1,14 +1,14 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (6.0.3.4)
|
||||
activesupport (6.0.6.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
zeitwerk (~> 2.2, >= 2.2.2)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
addressable (2.8.1)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
coffee-script (2.4.1)
|
||||
coffee-script-source
|
||||
execjs
|
||||
|
@ -16,7 +16,7 @@ GEM
|
|||
colorator (1.1.0)
|
||||
commonmarker (0.17.13)
|
||||
ruby-enum (~> 0.5)
|
||||
concurrent-ruby (1.1.7)
|
||||
concurrent-ruby (1.2.0)
|
||||
dnsruby (1.61.5)
|
||||
simpleidn (~> 0.1)
|
||||
em-websocket (0.5.2)
|
||||
|
@ -82,7 +82,7 @@ GEM
|
|||
octokit (~> 4.0)
|
||||
public_suffix (~> 3.0)
|
||||
typhoeus (~> 1.3)
|
||||
html-pipeline (2.14.0)
|
||||
html-pipeline (2.14.3)
|
||||
activesupport (>= 2)
|
||||
nokogiri (>= 1.4)
|
||||
http_parser.rb (0.6.0)
|
||||
|
@ -203,30 +203,32 @@ GEM
|
|||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.3)
|
||||
listen (3.3.0)
|
||||
listen (3.7.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
mercenary (0.3.6)
|
||||
mini_portile2 (2.4.0)
|
||||
mini_portile2 (2.8.8)
|
||||
minima (2.5.1)
|
||||
jekyll (>= 3.5, < 5.0)
|
||||
jekyll-feed (~> 0.9)
|
||||
jekyll-seo-tag (~> 2.1)
|
||||
minitest (5.14.2)
|
||||
minitest (5.17.0)
|
||||
multipart-post (2.1.1)
|
||||
nokogiri (1.10.10)
|
||||
mini_portile2 (~> 2.4.0)
|
||||
nokogiri (1.18.3)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
octokit (4.19.0)
|
||||
faraday (>= 0.9)
|
||||
sawyer (~> 0.8.0, >= 0.5.3)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (3.1.1)
|
||||
racc (1.8.1)
|
||||
rake (13.0.1)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.2.4)
|
||||
rexml (3.3.9)
|
||||
rouge (3.23.0)
|
||||
ruby-enum (0.8.0)
|
||||
i18n
|
||||
|
@ -248,13 +250,13 @@ GEM
|
|||
thread_safe (0.3.6)
|
||||
typhoeus (1.4.0)
|
||||
ethon (>= 0.9.0)
|
||||
tzinfo (1.2.8)
|
||||
tzinfo (1.2.11)
|
||||
thread_safe (~> 0.1)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.7)
|
||||
unicode-display_width (1.7.0)
|
||||
zeitwerk (2.4.1)
|
||||
zeitwerk (2.6.6)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
|
@ -11,7 +11,7 @@ search_enabled: true
|
|||
gh_edit_link: true
|
||||
gh_edit_link_text: "Edit this page on GitHub."
|
||||
gh_edit_repository: "https://github.com/cloudevents/sdk-java"
|
||||
gh_edit_branch: "master"
|
||||
gh_edit_branch: "main"
|
||||
gh_edit_source: docs
|
||||
gh_edit_view_mode: "tree"
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ binding for CloudEvents:
|
|||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -43,5 +43,5 @@ public class ProtonAmqpMessageFactory {
|
|||
The example uses the `vertx-proton` integration to send/receive CloudEvent
|
||||
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 AmqpClient](https://github.com/cloudevents/sdk-java/tree/master/examples/amqp-proton/src/main/java/io/cloudevents/examples/amqp/vertx/AmqpClient.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/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>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</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>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -34,7 +34,7 @@ final CloudEvent event = CloudEventBuilder.v1()
|
|||
.withId("000")
|
||||
.withType("example.demo")
|
||||
.withSource(URI.create("http://example.com"))
|
||||
.withData("text/plain","Hello world!".getBytes())
|
||||
.withData("text/plain","Hello world!".getBytes("UTF-8"))
|
||||
.build();
|
||||
```
|
||||
|
||||
|
@ -87,7 +87,7 @@ with Jackson, add `cloudevents-json-jackson` as a dependency and then using the
|
|||
`EventFormatProvider`:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.format.EventFormatProvider;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
|
||||
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.provider.ExtensionProvider;
|
||||
|
||||
DistributedTracingExtension dte = ExtensionsParser.getInstance()
|
||||
DistributedTracingExtension dte = ExtensionProvider.getInstance()
|
||||
.parseExtension(DistributedTracingExtension.class, event);
|
||||
```
|
||||
|
|
|
@ -27,7 +27,7 @@ HTTP Transport:
|
|||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-basic</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -48,6 +48,6 @@ public class HttpMessageFactory {
|
|||
|
||||
## 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)
|
||||
- [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 Servlet with Jetty](https://github.com/cloudevents/sdk-java/tree/master/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/JettyServer.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/main/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/HttpURLConnectionClient.java)
|
||||
- [Http Servlet with Jetty](https://github.com/cloudevents/sdk-java/tree/main/examples/basic-http/src/main/java/io/cloudevents/examples/http/basic/JettyServer.java)
|
||||
|
|
|
@ -0,0 +1,137 @@
|
|||
---
|
||||
title: CloudEvents HTTP Jakarta EE 9+ - Jakarta RESTful Web Services
|
||||
nav_order: 5
|
||||
---
|
||||
|
||||
# HTTP Protocol Binding for Jakarta EE 9+ - Jakarta RESTful Web Services
|
||||
|
||||
[](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Jakarta
|
||||
RESTful Web Services Binding for Jakarta EE 9+:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws-jakarta</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
This integration is tested with Jersey (Requires JDK11 or higher), RestEasy & Microprofile Liberty.
|
||||
|
||||
#### * Before using this package ensure your web framework does support the `jakarta.*` namespace.
|
||||
|
||||
## Receiving CloudEvents
|
||||
|
||||
You need to configure the `CloudEventsProvider` to enable
|
||||
marshalling/unmarshalling of CloudEvents.
|
||||
|
||||
Below is a sample on how to read and write CloudEvents:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
@Path("/")
|
||||
public class EventReceiverResource {
|
||||
|
||||
|
||||
|
||||
@GET
|
||||
@Path("getMinEvent")
|
||||
public CloudEvent getMinEvent() {
|
||||
return CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
}
|
||||
|
||||
// Return the CloudEvent using the HTTP binding structured encoding
|
||||
@GET
|
||||
@Path("getStructuredEvent")
|
||||
@StructuredEncoding("application/cloudevents+csv")
|
||||
public CloudEvent getStructuredEvent() {
|
||||
return CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("postEventWithoutBody")
|
||||
public Response postEvent(CloudEvent inputEvent) {
|
||||
// Handle the event
|
||||
return Response.ok().build();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending CloudEvents
|
||||
|
||||
You need to configure the `CloudEventsProvider` to enable
|
||||
marshalling/unmarshalling of CloudEvents.
|
||||
|
||||
Below is a sample on how to use the client to send a CloudEvent:
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.http.restful.ws.CloudEventsProvider;
|
||||
|
||||
import jakarta.ws.rs.client.Entity;
|
||||
import jakarta.ws.rs.client.WebTarget;
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
import jakarta.ws.rs.core.Response;
|
||||
|
||||
public class CloudEventSender {
|
||||
|
||||
public Response sendEvent(WebTarget target, CloudEvent event) {
|
||||
return target
|
||||
.path("postEvent")
|
||||
.request()
|
||||
.buildPost(Entity.entity(event, CloudEventsProvider.CLOUDEVENT_TYPE))
|
||||
.invoke();
|
||||
}
|
||||
|
||||
public Response sendEventAsStructured(WebTarget target, CloudEvent event) {
|
||||
return target
|
||||
.path("postEvent")
|
||||
.request()
|
||||
.buildPost(Entity.entity(event, "application/cloudevents+json"))
|
||||
.invoke();
|
||||
}
|
||||
|
||||
public CloudEvent getEvent(WebTarget target) {
|
||||
Response response = target
|
||||
.path("getEvent")
|
||||
.request()
|
||||
.buildGet()
|
||||
.invoke();
|
||||
|
||||
return response.readEntity(CloudEvent.class);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Migrating EE 8 applications to EE 9+
|
||||
The main change between Jakarta EE 8 and Jakarta EE 9 and future versions is the changing of the `javax.` to `jakarta.` namespaces used by key packages such as `jakarta.ws.rs-api` which provides the restful-ws API.
|
||||
|
||||
This change largely impacts only `import` statements it does filter down to dependencies such as this.
|
||||
|
||||
### Application migration
|
||||
For application migration we would recommend reviewing materials available from https://jakarta.ee/resources/#documentation as a starting point.
|
||||
|
||||
### CloudEvents Dependency
|
||||
To migrate to use EE 9+ supported package - replace `cloudevents-http-restful-ws` with `cloudevents-http-restful-ws-jakarta` and ensure the version is a minimum of `2.5.0-SNAPSHOT`
|
||||
|
||||
## Examples
|
||||
|
||||
- [Microprofile and Liberty](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-micropofile-liberty)
|
||||
|
|
@ -3,18 +3,18 @@ title: CloudEvents HTTP Jakarta RESTful Web Services
|
|||
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)
|
||||
|
||||
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
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -118,5 +118,5 @@ public class CloudEventSender {
|
|||
|
||||
## Examples
|
||||
|
||||
- [Quarkus and Resteasy](https://github.com/cloudevents/sdk-java/tree/master/examples/restful-ws-quarkus)
|
||||
- [Jersey and Spring Boot](https://github.com/cloudevents/sdk-java/tree/master/examples/restful-ws-spring-boot)
|
||||
- [Quarkus and Resteasy](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-quarkus)
|
||||
- [Jersey and Spring Boot](https://github.com/cloudevents/sdk-java/tree/main/examples/restful-ws-spring-boot)
|
||||
|
|
|
@ -14,7 +14,7 @@ HTTP Transport:
|
|||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-vertx</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -95,4 +95,4 @@ public class CloudEventClientVerticle extends AbstractVerticle {
|
|||
|
||||
## 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)
|
||||
|
|
|
@ -37,13 +37,20 @@ Using the Java SDK you can:
|
|||
| - [Jakarta Restful WS](http-jakarta-restful-ws.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Basic](http-basic.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Spring](spring.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [http4k][http4k]<sup>†</sup> | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jackson](json-jackson.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Proto](protobuf.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| XML Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [XML](xml.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| [Kafka Protocol Binding](kafka.md) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
|
||||
<sub>† Source/artifacts hosted externally</sub>
|
||||
|
||||
## Get Started
|
||||
|
||||
In order to start learning how to create, access and manipulate `CloudEvent`s,
|
||||
|
@ -57,7 +64,8 @@ receive CloudEvents, check out the dedicated pages:
|
|||
|
||||
- [AMQP using Proton](amqp-proton.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 Jackson](json-jackson.md)
|
||||
- [Kafka](kafka.md)
|
||||
|
@ -67,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).
|
||||
|
||||
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
|
||||
|
||||
|
@ -83,12 +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),
|
||||
`MessageReader` /`MessageWriter` to implement
|
||||
[Protocol bindings](https://github.com/cloudevents/spec/blob/v1.0/spec.md#protocol-binding)
|
||||
- [`cloudevents-bom`] Module providing a
|
||||
[bill of materials (BOM)](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#bill-of-materials-bom-poms)
|
||||
for easier integration of CloudEvents in other projects
|
||||
- [`cloudevents-json-jackson`] Implementation of [JSON Event format] with
|
||||
[Jackson](https://github.com/FasterXML/jackson)
|
||||
- [`cloudevents-protobuf`] Implementation of [Protobuf Event format] using code generated
|
||||
from the standard [protoc](https://github.com/protocolbuffers/protobuf) compiler.
|
||||
- [`cloudevents-xml`] Implementation of the XML Event Format.
|
||||
- [`cloudevents-http-vertx`] Implementation of [HTTP Protocol Binding] with
|
||||
[Vert.x Core](https://vertx.io/)
|
||||
- [`cloudevents-http-restful-ws`] Implementation of [HTTP Protocol Binding]
|
||||
for [Jakarta 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
|
||||
Binding], primarily intended for integrators
|
||||
- [`cloudevents-kafka`] Implementation of [Kafka Protocol Binding]
|
||||
|
@ -101,15 +117,21 @@ You can look at the latest published artifacts on
|
|||
[Maven Central](https://search.maven.org/search?q=g:io.cloudevents).
|
||||
|
||||
[JSON Event format]: https://github.com/cloudevents/spec/blob/v1.0/json-format.md
|
||||
[Protobuf Event format]: https://github.com/cloudevents/spec/blob/v1.0.1/protobuf-format.md
|
||||
[HTTP Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/http-protocol-binding.md
|
||||
[Kafka Protocol Binding]: https://github.com/cloudevents/spec/blob/v1.0/kafka-protocol-binding.md
|
||||
[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-core`]: https://github.com/cloudevents/sdk-java/tree/master/core
|
||||
[`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/master/formats/json-jackson
|
||||
[`cloudevents-http-vertx`]: https://github.com/cloudevents/sdk-java/tree/master/http/vertx
|
||||
[`cloudevents-http-basic`]: https://github.com/cloudevents/sdk-java/tree/master/http/basic
|
||||
[`cloudevents-http-restful-ws`]: https://github.com/cloudevents/sdk-java/tree/master/http/restful-ws
|
||||
[`cloudevents-kafka`]: https://github.com/cloudevents/sdk-java/tree/master/kafka
|
||||
[`cloudevents-amqp-proton`]: https://github.com/cloudevents/sdk-java/tree/master/amqp
|
||||
[`cloudevents-spring`]: https://github.com/cloudevents/sdk-java/tree/master/spring
|
||||
[`cloudevents-api`]: https://github.com/cloudevents/sdk-java/tree/main/api
|
||||
[`cloudevents-bom`]: https://github.com/cloudevents/sdk-java/tree/main/bom
|
||||
[`cloudevents-core`]: https://github.com/cloudevents/sdk-java/tree/main/core
|
||||
[`cloudevents-json-jackson`]: https://github.com/cloudevents/sdk-java/tree/main/formats/json-jackson
|
||||
[`cloudevents-protobuf`]: https://github.com/cloudevents/sdk-java/tree/main/formats/protobuf
|
||||
[`cloudevents-xml`]: https://github.com/cloudevents/sdk-java/tree/main/formats/xml
|
||||
[`cloudevents-http-vertx`]: https://github.com/cloudevents/sdk-java/tree/main/http/vertx
|
||||
[`cloudevents-http-basic`]: https://github.com/cloudevents/sdk-java/tree/main/http/basic
|
||||
[`cloudevents-http-restful-ws`]: https://github.com/cloudevents/sdk-java/tree/main/http/restful-ws
|
||||
[`cloudevents-http-restful-ws-jakarta`]: https://github.com/cloudevents/sdk-java/tree/main/http/restful-ws-jakarta
|
||||
[`cloudevents-kafka`]: https://github.com/cloudevents/sdk-java/tree/main/kafka
|
||||
[`cloudevents-amqp-proton`]: https://github.com/cloudevents/sdk-java/tree/main/amqp
|
||||
[`cloudevents-spring`]: https://github.com/cloudevents/sdk-java/tree/main/spring
|
||||
[http4k]: https://www.http4k.org/guide/modules/cloud_events/
|
||||
|
|
|
@ -17,7 +17,7 @@ For Maven based projects, use the following dependency:
|
|||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-json-jackson</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -28,9 +28,9 @@ adding the dependency to your project:
|
|||
|
||||
```java
|
||||
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.jackson.JsonFormat;
|
||||
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
|
@ -40,7 +40,7 @@ CloudEvent event = CloudEventBuilder.v1()
|
|||
|
||||
byte[]serialized = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(JsonFormat.CONTENT_TYPE)
|
||||
.resolveFormat(ContentType.JSON)
|
||||
.serialize(event);
|
||||
```
|
||||
|
||||
|
|
|
@ -10,14 +10,14 @@ nav_order: 5
|
|||
Implementation of Kafka Protocol Binding to send and receive CloudEvents.
|
||||
|
||||
For Maven based projects, use the following to configure the
|
||||
[Kafka Protocol Binding](https://github.com/cloudevents/spec/blob/master/kafka-protocol-binding.md):
|
||||
[Kafka Protocol Binding](https://github.com/cloudevents/spec/blob/main/kafka-protocol-binding.md):
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
@ -64,7 +64,7 @@ public class CloudEventProducer {
|
|||
You can configure the Encoding and EventFormat to use to emit the event.
|
||||
|
||||
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.
|
||||
|
||||
### Partition key extension
|
||||
|
@ -81,7 +81,7 @@ producerProps.put(
|
|||
|
||||
When using in your producer, this interceptor will pick the `partitionkey`
|
||||
extension from the event and will set it as record key, regardless of the input record key.
|
||||
Check out the [`PartitionKeyExtensionInterceptor`](https://github.com/cloudevents/sdk-java/tree/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.
|
||||
|
||||
## Consuming CloudEvents
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
title: CloudEvents Protocol Buffers
|
||||
nav_order: 4
|
||||
---
|
||||
|
||||
# CloudEvents Protocol Buffers
|
||||
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
|
||||
|
||||
This module provides the Protocol Buffer (protobuf) `EventFormat` implementation using the Java
|
||||
Protobuf runtime and classes generated from the CloudEvents
|
||||
[proto spec](https://github.com/cloudevents/spec/blob/v1.0.1/spec.proto).
|
||||
|
||||
# Setup
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-protobuf</artifactId>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
No further configuration is required is use the module.
|
||||
|
||||
## Using the Protobuf Event Format
|
||||
|
||||
### Event serialization
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.format.ContentType;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.vertx")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.build();
|
||||
|
||||
byte[]serialized = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(ContentType.PROTO)
|
||||
.serialize(event);
|
||||
```
|
||||
|
||||
The `EventFormatProvider` will automatically resolve the `ProtobufFormat` using the
|
||||
`ServiceLoader` APIs.
|
||||
|
||||
## Passing Protobuf messages as CloudEvent data.
|
||||
|
||||
The `ProtoCloudEventData` capability provides a convenience mechanism to handle Protobuf message object data.
|
||||
|
||||
### Building
|
||||
|
||||
```java
|
||||
// Build my business event message.
|
||||
com.google.protobuf.Message myMessage = ..... ;
|
||||
|
||||
// Wrap the protobuf message as CloudEventData.
|
||||
CloudEventData ceData = ProtoCloudEventData.wrap(myMessage);
|
||||
|
||||
// Build the CloudEvent
|
||||
CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("hello")
|
||||
.withType("example.protodata")
|
||||
.withSource(URI.create("http://localhost"))
|
||||
.withData(ceData)
|
||||
.build();
|
||||
```
|
||||
|
||||
### Reading
|
||||
|
||||
If the `ProtobufFormat` is used to deserialize a CloudEvent that contains a protobuf message object as data you can use
|
||||
the `ProtoCloudEventData` to access it as an 'Any' directly.
|
||||
|
||||
```java
|
||||
|
||||
// Deserialize the event.
|
||||
CloudEvent myEvent = eventFormat.deserialize(raw);
|
||||
|
||||
// Get the Data
|
||||
CloudEventData eventData = myEvent.getData();
|
||||
|
||||
if (ceData instanceOf ProtoCloudEventData) {
|
||||
|
||||
// Obtain the protobuf 'any'
|
||||
Any anAny = ((ProtoCloudEventData) eventData).getAny();
|
||||
|
||||
...
|
||||
}
|
||||
|
||||
```
|
||||
|
168
docs/spring.md
168
docs/spring.md
|
@ -8,7 +8,7 @@ nav_order: 5
|
|||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-spring)
|
||||
|
||||
This module provides the integration of `CloudEvent` with different Spring APIs,
|
||||
like MVC, WebFlux and Messaging
|
||||
like MVC, WebFlux, RSocket and Messaging
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
|
||||
|
@ -17,10 +17,12 @@ For Maven based projects, use the following dependency:
|
|||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-spring</artifactId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.0.1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
plus whatever you need to support your use case (e.g. `spring-boot-starter-webflux` for reactive HTTP).
|
||||
|
||||
## Introduction
|
||||
|
||||
This module provides classes and interfaces that can be used by
|
||||
|
@ -38,8 +40,166 @@ details).
|
|||
|
||||
## Examples
|
||||
|
||||
Check out the samples:
|
||||
### Spring MVC
|
||||
|
||||
- [spring-reactive](https://github.com/cloudevents/sdk-java/tree/master/examples/spring-reactive)
|
||||
There is a `CloudEventHttpMessageConverter` that you can register for Spring MVC:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public static class CloudEventHandlerConfiguration implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
|
||||
converters.add(0, new CloudEventHttpMessageConverter());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
With this in place you can write a `@RestController` with `CloudEvent` inputs or outputs, and the conversion will be handled by Spring. Example "echo" endpoint:
|
||||
|
||||
```java
|
||||
@PostMapping("/echo")
|
||||
public CloudEvent ce(@RequestBody CloudEvent event) {
|
||||
return CloudEventBuilder.from(event)
|
||||
.withId(UUID.randomUUID().toString())
|
||||
.withSource(URI.create("https://spring.io/foos"))
|
||||
.withType("io.spring.event.Foo")
|
||||
.withData(event.getData().toBytes())
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
Both structured and binary events are supported. So if you know that the `CloudEvent` is in binary mode and the data can be converted to a `Foo`, then you can also use the `CloudEventHttpUtils` to deal with HTTP headers and stick to POJOs in the handler method. Example:
|
||||
|
||||
```java
|
||||
@PostMapping("/echo")
|
||||
public ResponseEntity<Foo> echo(@RequestBody Foo foo, @RequestHeader HttpHeaders headers) {
|
||||
CloudEvent attributes = CloudEventHttpUtils.fromHttp(headers)
|
||||
.withId(UUID.randomUUID().toString())
|
||||
.withSource(URI.create("https://spring.io/foos"))
|
||||
.withType("io.spring.event.Foo")
|
||||
.build();
|
||||
HttpHeaders outgoing = CloudEventHttpUtils.toHttp(attributes);
|
||||
return ResponseEntity.ok().headers(outgoing).body(foo);
|
||||
}
|
||||
```
|
||||
|
||||
### Spring Webflux
|
||||
|
||||
If you are using Spring Webflux instead of Spring MVC you can use the same patterns, but the configuration is different. In this case we have a pair of readers and writers that you can register with the `CodecCustomizer`:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public static class CloudEventHandlerConfiguration implements CodecCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(CodecConfigurer configurer) {
|
||||
configurer.customCodecs().register(new CloudEventHttpMessageReader());
|
||||
configurer.customCodecs().register(new CloudEventHttpMessageWriter());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
Then you can write similar code to the MVC example above, but with reactive signatures. Example echo endpoint:
|
||||
|
||||
```java
|
||||
@PostMapping("/event")
|
||||
public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
|
||||
return body.map(event -> CloudEventBuilder.from(event)
|
||||
.withId(UUID.randomUUID().toString())
|
||||
.withSource(URI.create("https://spring.io/foos"))
|
||||
.withType("io.spring.event.Foo")
|
||||
.withData(event.getData().toBytes()).build());
|
||||
}
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public static class CloudEventMessageConverterConfiguration {
|
||||
@Bean
|
||||
public CloudEventMessageConverter cloudEventMessageConverter() {
|
||||
return new CloudEventMessageConverter();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A simple echo with Spring Cloud Function could then be written as:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public Function<CloudEvent, CloudEvent> events() {
|
||||
return event -> CloudEventBuilder.from(event)
|
||||
.withId(UUID.randomUUID().toString())
|
||||
.withSource(URI.create("https://spring.io/foos"))
|
||||
.withType("io.spring.event.Foo")
|
||||
.withData(event.getData().toBytes())
|
||||
.build();
|
||||
}
|
||||
```
|
||||
|
||||
(If the application was a webapp with `spring-cloud-function-web` you would need the HTTP converters or codecs as well, per the example above.)
|
||||
|
||||
### Generic Encoder and Decoder
|
||||
|
||||
Some applications present Cloud Events as binary data, but do not have "headers" like in HTTP or messages. For those use cases there is a lower level construct in Spring, and this project provides implementations in the form of `CloudEventEncoder` and `CloudEventDecoder`. Since the headers are not available in the surrounding abstraction, these only support _structured_ Cloud Events, where the attributes and data are packed together in the same byte buffer. As an example in an RSockets application you can register them like this:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@Order(-1)
|
||||
public RSocketStrategiesCustomizer cloudEventsCustomizer() {
|
||||
return new RSocketStrategiesCustomizer() {
|
||||
@Override
|
||||
public void customize(Builder strategies) {
|
||||
strategies.encoder(new CloudEventEncoder());
|
||||
strategies.decoder(new CloudEventDecoder());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
and then a simple echo endpoint could be written like this:
|
||||
|
||||
```java
|
||||
@MessageMapping("event")
|
||||
public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
|
||||
return body.map(event -> CloudEventBuilder.from(event)
|
||||
.withId(UUID.randomUUID().toString())
|
||||
.withSource(URI.create("https://spring.io/foos"))
|
||||
.withType("io.spring.event.Foo")
|
||||
.withData(event.getData().toBytes())
|
||||
.build());
|
||||
}
|
||||
```
|
||||
|
||||
### More
|
||||
|
||||
Check out the integration tests and samples:
|
||||
|
||||
- [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
|
||||
Webflux.
|
||||
|
||||
- [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.
|
||||
|
||||
- [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.
|
||||
|
|
|
@ -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>
|
||||
<artifactId>cloudevents-examples</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cloudevents-amqp-proton-example</artifactId>
|
||||
|
||||
<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>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.apache.qpid.proton.message.Message;
|
|||
|
||||
import java.io.PrintWriter;
|
||||
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.
|
||||
|
@ -71,7 +72,7 @@ public class AmqpClient {
|
|||
.withSource(URI.create("http://127.0.0.1/amqp-client"))
|
||||
.withType("com.example.sampletype1")
|
||||
.withTime(Time.parseTime("2020-11-06T21:47:12.037467+00:00"))
|
||||
.withData(payload.toString().getBytes())
|
||||
.withData(payload.toString().getBytes(StandardCharsets.UTF_8))
|
||||
.build();
|
||||
|
||||
final Message message = ProtonAmqpMessageFactory.createWriter().writeBinary(event);
|
||||
|
|
|
@ -2,6 +2,7 @@ package io.cloudevents.examples.amqp.vertx;
|
|||
|
||||
import java.io.PrintWriter;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
@ -81,7 +82,7 @@ public class AmqpServer {
|
|||
.withType("com.example.sampletype1")
|
||||
.withSource(URI.create("http://127.0.0.1/amqp-server"))
|
||||
.withTime(OffsetDateTime.now())
|
||||
.withData("{\"temp\": 5}".getBytes())
|
||||
.withData("{\"temp\": 5}".getBytes(StandardCharsets.UTF_8))
|
||||
.build();
|
||||
|
||||
final Message message = writer.writeBinary(event);
|
||||
|
|
|
@ -21,11 +21,14 @@
|
|||
<parent>
|
||||
<artifactId>cloudevents-examples</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cloudevents-basic-http-example</artifactId>
|
||||
<properties>
|
||||
<module-name>cloudevents.example.basic.http</module-name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
@ -41,7 +44,7 @@
|
|||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<version>9.4.35.v20201120</version>
|
||||
<version>9.4.55.v20240627</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<parent>
|
||||
<artifactId>cloudevents-examples</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cloudevents-kafka-example</artifactId>
|
||||
<properties>
|
||||
<module-name>cloudevents.example.kafka</module-name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.apache.kafka.clients.producer.RecordMetadata;
|
|||
import org.apache.kafka.common.serialization.StringSerializer;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Properties;
|
||||
import java.util.UUID;
|
||||
|
||||
|
@ -33,7 +34,7 @@ public class SampleProducer {
|
|||
|
||||
// Configure the CloudEventSerializer to emit events as json structured events
|
||||
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);
|
||||
|
||||
// Create the KafkaProducer
|
||||
|
@ -42,7 +43,7 @@ public class SampleProducer {
|
|||
|
||||
// Create an event template to set basic CloudEvent attributes
|
||||
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");
|
||||
|
||||
for (int i = 0; i < MESSAGE_COUNT; i++) {
|
||||
|
@ -53,7 +54,7 @@ public class SampleProducer {
|
|||
// Create the event starting from the template
|
||||
CloudEvent event = eventTemplate.newBuilder()
|
||||
.withId(id)
|
||||
.withData("text/plain", data.getBytes())
|
||||
.withData("text/plain", data.getBytes(StandardCharsets.UTF_8))
|
||||
.build();
|
||||
|
||||
// Send the record
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -21,11 +21,15 @@
|
|||
<modules>
|
||||
<module>kafka</module>
|
||||
<module>restful-ws-quarkus</module>
|
||||
<module>restful-ws-microprofile-liberty</module>
|
||||
<module>vertx</module>
|
||||
<module>basic-http</module>
|
||||
<module>restful-ws-spring-boot</module>
|
||||
<module>amqp-proton</module>
|
||||
<module>spring-reactive</module>
|
||||
<module>spring-rsocket</module>
|
||||
<module>spring-function</module>
|
||||
<module>rocketmq</module>
|
||||
</modules>
|
||||
|
||||
</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
|
||||
|
||||
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
|
||||
curl -v http://localhost:8080/users \
|
||||
|
@ -30,6 +34,29 @@ curl -v 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
|
||||
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>
|
||||
<artifactId>cloudevents-examples</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>cloudevents-restful-ws-quarkus-example</artifactId>
|
||||
<properties>
|
||||
<module-name>cloudevents.example.restful.ws.quarkus</module-name>
|
||||
<quarkus-plugin.version>1.10.3.Final</quarkus-plugin.version>
|
||||
<quarkus.platform.artifact-id>quarkus-universe-bom</quarkus.platform.artifact-id>
|
||||
<quarkus.platform.group-id>io.quarkus</quarkus.platform.group-id>
|
||||
|
|
|
@ -1,18 +1,24 @@
|
|||
package io.cloudevents.examples.quarkus.client;
|
||||
|
||||
import javax.ws.rs.Consumes;
|
||||
import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@Path("/users")
|
||||
@RegisterRestClient
|
||||
public interface UserClient {
|
||||
|
||||
// This will emit binary encoded events.
|
||||
// To use structured JSON encoding use @Consumes(JsonFormat.CONTENT_TYPE).
|
||||
@POST
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
void emit(CloudEvent event);
|
||||
void emitBinary(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.examples.quarkus.model.User;
|
||||
import io.quarkus.scheduler.Scheduled;
|
||||
import io.smallrye.mutiny.Uni;
|
||||
|
||||
@ApplicationScoped
|
||||
public class UserEventsGenerator {
|
||||
|
@ -38,8 +37,13 @@ public class UserEventsGenerator {
|
|||
@Scheduled(every="2s")
|
||||
public void init() {
|
||||
CloudEvent event = createEvent(userCount++);
|
||||
LOGGER.info("try to emit user: {}", event.getId());
|
||||
userClient.emit(event);
|
||||
if(userCount % 2 == 0) {
|
||||
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) {
|
||||
|
|
|
@ -3,6 +3,7 @@ package io.cloudevents.examples.quarkus.resources;
|
|||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.examples.quarkus.model.User;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
import io.cloudevents.jackson.PojoCloudEventDataMapper;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -17,7 +18,7 @@ import java.util.HashMap;
|
|||
import java.util.Map;
|
||||
|
||||
@Path("/users")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
@Consumes({MediaType.APPLICATION_JSON, JsonFormat.CONTENT_TYPE})
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public class UserResource {
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import io.quarkus.test.junit.QuarkusTest;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
|
||||
@QuarkusTest
|
||||
public class UserResourceTest {
|
||||
|
|
|
@ -11,11 +11,11 @@ mvn spring-boot:run
|
|||
You can try sending a request using `curl`:
|
||||
|
||||
```shell
|
||||
curl -X POST -v -d '{"username": "slinkydeveloper", "firstName": "Francesco", "lastName": "Guardiani", "age": 23}' \ ~
|
||||
-H'Content-type: application/json' \
|
||||
-H'Ce-id: 1' \
|
||||
-H'Ce-source: cloud-event-example' \
|
||||
-H'Ce-type: happybirthday.myapplication' \
|
||||
-H'Ce-specversion: 1.0' \
|
||||
curl -X POST -v -d '{"username": "slinkydeveloper", "firstName": "Francesco", "lastName": "Guardiani", "age": 23}' \
|
||||
-H 'Content-type: application/json' \
|
||||
-H 'Ce-id: 1' \
|
||||
-H 'Ce-source: cloud-event-example' \
|
||||
-H 'Ce-type: happybirthday.myapplication' \
|
||||
-H 'Ce-specversion: 1.0' \
|
||||
http://localhost:8080/happy_birthday
|
||||
```
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
<parent>
|
||||
<artifactId>cloudevents-examples</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cloudevents-spring-boot-example</artifactId>
|
||||
|
||||
<properties>
|
||||
<module-name>cloudevents.example.spring.boot</module-name>
|
||||
<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>
|
||||
|
||||
<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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
# Spring Reactive + CloudEvents sample
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mvn package
|
||||
```
|
||||
|
||||
## Start HTTP Server
|
||||
|
||||
```shell
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
You can try sending a request using curl, and it echos back a cloud event the same body and with new `ce-*` headers:
|
||||
|
||||
```shell
|
||||
curl -v -d '{"value": "Foo"}' \
|
||||
-H'Content-type: application/json' \
|
||||
-H'ce-id: 1' \
|
||||
-H'ce-source: cloud-event-example' \
|
||||
-H'ce-type: my.application.Foo' \
|
||||
-H'ce-specversion: 1.0' \
|
||||
http://localhost:8080/event
|
||||
```
|
||||
|
||||
It also accepts data in "structured" format:
|
||||
|
||||
```shell
|
||||
curl -v -H'Content-type: application/cloudevents+json' \
|
||||
-d '{"data": {"value": "Foo"},
|
||||
"id": "1",
|
||||
"source": "cloud-event-example",
|
||||
"type": "my.application.Foo",
|
||||
"specversion": "1.0"}' \
|
||||
http://localhost:8080/event
|
||||
```
|
||||
|
||||
The `/event endpoint is implemented like this (the request and response are modelled directly as a `CloudEvent`):
|
||||
|
||||
```java
|
||||
@PostMapping("/event")
|
||||
public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
|
||||
return ...;
|
||||
}
|
||||
```
|
||||
|
||||
and to make that work we need to install the codecs:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public static class CloudEventHandlerConfiguration implements CodecCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(CodecConfigurer configurer) {
|
||||
configurer.customCodecs().register(new CloudEventHttpMessageReader());
|
||||
configurer.customCodecs().register(new CloudEventHttpMessageWriter());
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
The same feature in Spring MVC is provided by the `CloudEventHttpMessageConverter`.
|
||||
|
||||
|
||||
The `/foos` endpoint does the same thing. It doesn't use the `CloudEvent` data type directly, but instead models the request and response body as a `Foo` (POJO type):
|
||||
|
||||
```java
|
||||
@PostMapping("/foos")
|
||||
public ResponseEntity<Foo> echo(@RequestBody Foo foo, @RequestHeader HttpHeaders headers) {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Note that this endpoint only accepts "binary" format cloud events (context in HTTP headers like in the first example above). It translates the `HttpHeaders` to `CloudEventContext` using a utility class provided by `cloudevents-spring`.
|
|
@ -0,0 +1,73 @@
|
|||
<?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-spring-function-example</artifactId>
|
||||
|
||||
<properties>
|
||||
<module-name>cloudevents.example.spring.function</module-name>
|
||||
<spring-boot.version>2.4.3</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-dependencies</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-function-web</artifactId>
|
||||
<version>3.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-spring</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-json-jackson</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>${spring-boot.version}</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,64 @@
|
|||
package io.cloudevents.examples.spring;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.web.codec.CodecCustomizer;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.codec.CodecConfigurer;
|
||||
|
||||
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
|
||||
public class DemoApplication {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
SpringApplication.run(DemoApplication.class, args);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Function<CloudEvent, CloudEvent> events() {
|
||||
return event -> CloudEventBuilder.from(event)
|
||||
.withId(UUID.randomUUID().toString())
|
||||
.withSource(URI.create("https://spring.io/foos"))
|
||||
.withType("io.spring.event.Foo")
|
||||
.withData(event.getData().toBytes())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure a MessageConverter for Spring Cloud Function to pick up and use to
|
||||
* convert to and from CloudEvent and Message.
|
||||
*/
|
||||
@Configuration
|
||||
public static class CloudEventMessageConverterConfiguration {
|
||||
@Bean
|
||||
public CloudEventMessageConverter cloudEventMessageConverter() {
|
||||
return new CloudEventMessageConverter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure an HTTP reader and writer so that we can process CloudEvents over
|
||||
* HTTP via Spring Webflux.
|
||||
*/
|
||||
@Configuration
|
||||
public static class CloudEventHandlerConfiguration implements CodecCustomizer {
|
||||
|
||||
@Override
|
||||
public void customize(CodecConfigurer configurer) {
|
||||
configurer.customCodecs().register(new CloudEventHttpMessageReader());
|
||||
configurer.customCodecs().register(new CloudEventHttpMessageWriter());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright 2019-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudevents.examples.spring;
|
||||
|
||||
class Foo {
|
||||
|
||||
private String value;
|
||||
|
||||
public Foo() {
|
||||
}
|
||||
|
||||
public Foo(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void setValue(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Foo [value=" + this.value + "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package io.cloudevents.examples.spring;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
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.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.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class DemoApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate rest;
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
@Test
|
||||
void echoWithCorrectHeaders() {
|
||||
|
||||
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);
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void structuredRequestResponseEvents() {
|
||||
|
||||
ResponseEntity<String> response = rest
|
||||
.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/event")) //
|
||||
.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);
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -29,10 +29,10 @@ It also accepts data in "structured" format:
|
|||
```shell
|
||||
curl -v -H'Content-type: application/cloudevents+json' \
|
||||
-d '{"data": {"value": "Foo"},
|
||||
"ce-id: 1,
|
||||
"ce-source": "cloud-event-example"
|
||||
"ce-type": "my.application.Foo"
|
||||
"ce-specversion": "1.0"}' \
|
||||
"id: 1,
|
||||
"source": "cloud-event-example"
|
||||
"type": "my.application.Foo"
|
||||
"specversion": "1.0"}' \
|
||||
http://localhost:8080/event
|
||||
```
|
||||
|
||||
|
@ -72,4 +72,4 @@ public ResponseEntity<Foo> echo(@RequestBody Foo foo, @RequestHeader HttpHeaders
|
|||
}
|
||||
```
|
||||
|
||||
Note that this endpoint only accepts "binary" format cloud events (context in HTTP headers like in the first example above). It translates the `HttpHeaders` to `CloudEventContext` using a utility class provided by `cloudevents-spring`.
|
||||
Note that this endpoint only accepts "binary" format cloud events (context in HTTP headers like in the first example above). It translates the `HttpHeaders` to `CloudEventContext` using a utility class provided by `cloudevents-spring`.
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
<parent>
|
||||
<artifactId>cloudevents-examples</artifactId>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<version>2.0.0</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>cloudevents-spring-reactive-example</artifactId>
|
||||
|
||||
<properties>
|
||||
<spring-boot.version>2.4.0</spring-boot.version>
|
||||
<module-name>cloudevents.example.spring.reactive</module-name>
|
||||
<spring-boot.version>2.4.3</spring-boot.version>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
|
|
|
@ -1,112 +1,149 @@
|
|||
package io.cloudevents.examples.spring;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
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.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.RequestEntity;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
|
||||
public class DemoApplicationTests {
|
||||
|
||||
@Autowired
|
||||
private TestRestTemplate rest;
|
||||
@Autowired
|
||||
private WebTestClient rest;
|
||||
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
@Test
|
||||
void echoWithCorrectHeaders() {
|
||||
|
||||
@Test
|
||||
void echoWithCorrectHeaders() {
|
||||
rest.post().uri("/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) //
|
||||
.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);
|
||||
assertThat(response.getBody()).isEqualTo("{\"value\":\"Dave\"}");
|
||||
@Test
|
||||
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");
|
||||
assertThat(headers.getFirst("ce-type")).isEqualTo("io.spring.event.Foo");
|
||||
assertThat(headers.getFirst("ce-source")).isEqualTo("https://spring.io/foos");
|
||||
@Test
|
||||
void structuredRequestResponseCloudEventToString() {
|
||||
|
||||
}
|
||||
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
|
||||
.exchange(RequestEntity.post(URI.create("http://localhost:" + port + "/event")) //
|
||||
.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);
|
||||
@Test
|
||||
void structuredRequestResponseCloudEventToCloudEvent() {
|
||||
|
||||
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
|
||||
assertThat(response.getBody()).isEqualTo("{\"value\":\"Dave\"}");
|
||||
rest.post().uri("/event") //
|
||||
.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");
|
||||
assertThat(headers).containsKey("ce-source");
|
||||
assertThat(headers).containsKey("ce-type");
|
||||
@Test
|
||||
void requestResponseEvents() {
|
||||
|
||||
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");
|
||||
rest.post().uri("/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) //
|
||||
.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());
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
# Spring RSockets + CloudEvents sample
|
||||
|
||||
## Build
|
||||
|
||||
```shell
|
||||
mvn package
|
||||
```
|
||||
|
||||
## Start Server
|
||||
|
||||
```shell
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
You can try sending a request using [rsc](https://github.com/making/rsc), and it echos back a cloud event the same body and with new `ce-*` headers:
|
||||
|
||||
```shell
|
||||
rsc --request --dataMimeType=application/cloudevents+json --route=event \
|
||||
--data='{"data": {"value": "Foo"},
|
||||
"id": "1",
|
||||
"source": "cloud-event-example",
|
||||
"type": "my.application.Foo",
|
||||
"specversion": "1.0"}' \
|
||||
--debug tcp://localhost:7000
|
||||
```
|
||||
|
||||
The `event` endpoint is implemented like this (the request and response are modelled directly as a `CloudEvent`):
|
||||
|
||||
```java
|
||||
@MessageMapping("event")
|
||||
public Mono<CloudEvent> event(@RequestBody Mono<CloudEvent> body) {
|
||||
return ...;
|
||||
}
|
||||
```
|
||||
|
||||
and to make that work we need to install the codecs:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
@Order(-1)
|
||||
public RSocketStrategiesCustomizer cloudEventsCustomizer() {
|
||||
return new RSocketStrategiesCustomizer() {
|
||||
@Override
|
||||
public void customize(Builder strategies) {
|
||||
strategies.encoder(new CloudEventEncoder());
|
||||
strategies.decoder(new CloudEventDecoder());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
```
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue