Compare commits
243 Commits
2.0.0-mile
...
main
Author | SHA1 | Date |
---|---|---|
|
0d96691f6d | |
|
c8a22b5175 | |
|
57f9867a5c | |
|
43f0d5b138 | |
|
efe7e01b75 | |
|
188c3c7085 | |
|
659bb8cc01 | |
|
9981635e7d | |
|
b54df5ca0c | |
|
a3decf768c | |
|
215e67f2c3 | |
|
514e00d75b | |
|
f7ac215bf1 | |
|
5f4abd144a | |
|
8d59d29bb1 | |
|
6ab42de007 | |
|
2e87988d0c | |
|
6d958b69d4 | |
|
1de03ad38a | |
|
4c5d0efb89 | |
|
01a9111d8b | |
|
e056d1b2d8 | |
|
efdf0ba866 | |
|
bd90d903ce | |
|
52a98d778c | |
|
010627e784 | |
|
a7904823c3 | |
|
e5a35ac472 | |
|
4d204cef89 | |
|
43afb6ceaa | |
|
9c6e7fd11e | |
|
98deac1599 | |
|
05baf9be8a | |
|
3add823e00 | |
|
5a03173dde | |
|
9ee16fb48c | |
|
0db4446163 | |
|
111fb55cfd | |
|
55fddb35fc | |
|
7b9d020acc | |
|
fb11b94f2b | |
|
eaef3becdd | |
|
b30324e916 | |
|
1f9fa13231 | |
|
a135755ec6 | |
|
677a2c2628 | |
|
826e099fc0 | |
|
76366338fc | |
|
4ef304115a | |
|
582feed520 | |
|
5ef1088a19 | |
|
698cdf7ad4 | |
|
4ebeab0e0f | |
|
4c81f3eacc | |
|
569e025cf0 | |
|
3614a4f5f4 | |
|
d64aff7327 | |
|
d1cff75230 | |
|
1591cb337a | |
|
f71303b7b7 | |
|
d59b33307a | |
|
e0d1961f35 | |
|
7c6b52ab30 | |
|
433ec5b274 | |
|
40fe91a5e0 | |
|
a43f90f4e2 | |
|
e488269510 | |
|
7d50c7fc7a | |
|
f1a86af656 | |
|
354f7a16ef | |
|
9132a13d81 | |
|
a491c85eb2 | |
|
6362bfbcd6 | |
|
f08a099ed9 | |
|
4139fb7e57 | |
|
adde53c817 | |
|
0dc10251ff | |
|
b9eaa2fcaa | |
|
f8d27b08bf | |
|
9125136530 | |
|
f35e6e610a | |
|
45ec85f8c1 | |
|
1d87fb7191 | |
|
8f9b741306 | |
|
2dd8ba95dd | |
|
1c29726e8c | |
|
6681205733 | |
|
a4bc7a8368 | |
|
4784f03e8c | |
|
ace6859ae0 | |
|
cc786251d5 | |
|
ceb06757a3 | |
|
8d91cdaee6 | |
|
d00ad967c0 | |
|
9231e6d230 | |
|
a94bc5c81c | |
|
32adfe9123 | |
|
0277ee4ae4 | |
|
202849307c | |
|
624ac693d8 | |
|
5a323942d3 | |
|
1587708805 | |
|
06c4ec5385 | |
|
73a3c370d5 | |
|
722f5205b3 | |
|
8ad857d8c7 | |
|
baa9b5927a | |
|
c41a2c3ba7 | |
|
3651cdae18 | |
|
ee4c85b1a1 | |
|
a0b0835180 | |
|
687e03bac5 | |
|
208b18c299 | |
|
9d45943844 | |
|
0a1a03db64 | |
|
7f355d10c1 | |
|
3234e30e55 | |
|
c8f10e9215 | |
|
ba9ccad5d2 | |
|
78355bb225 | |
|
2730ae4a13 | |
|
30fd6769eb | |
|
ff07dd8315 | |
|
928ebcfd6f | |
|
a4613c00d2 | |
|
69f0e20549 | |
|
e2b13109e4 | |
|
5e3bfc890f | |
|
13f8b56618 | |
|
a419d8bba3 | |
|
47bed5616d | |
|
baba37ccfd | |
|
32bcdcd3b9 | |
|
8b382734d9 | |
|
f05418cba9 | |
|
23cd08fcfd | |
|
70782da2c2 | |
|
48fc69e058 | |
|
e7e6e46bd5 | |
|
e523bfbfbf | |
|
12eee4da6e | |
|
d49ff9f69d | |
|
611f2292a7 | |
|
296230719b | |
|
d9592d5201 | |
|
00cdf9cb42 | |
|
bd11010138 | |
|
3a22557b83 | |
|
87c6915d9a | |
|
a7f87cf6cb | |
|
711277eacb | |
|
58570cf4d9 | |
|
24d108fe5d | |
|
f5d9b47c1c | |
|
59643c3368 | |
|
34483025df | |
|
e9d15daf28 | |
|
b89f45265b | |
|
a14f5eabec | |
|
5099b31f6c | |
|
bcc1434a39 | |
|
c1c55ac1d6 | |
|
5ca2c2de91 | |
|
2524cdf324 | |
|
eeb83c3567 | |
|
5a926820b4 | |
|
7c0b1e3c49 | |
|
377850cb61 | |
|
d2a89a4e08 | |
|
de786322aa | |
|
5037a69a80 | |
|
ceb5a2eeb2 | |
|
78a023d08c | |
|
bbce65aa64 | |
|
cc0892a440 | |
|
2d68c4843f | |
|
7f65c92dec | |
|
c9b3fa4b65 | |
|
394347db07 | |
|
6c78428513 | |
|
bf60a25098 | |
|
baf3b56d6d | |
|
8ac23dc479 | |
|
84fbb0c801 | |
|
20ebdbf87f | |
|
2411fe3508 | |
|
554198d9e0 | |
|
c1ff628511 | |
|
62fe155604 | |
|
34408236db | |
|
7696ffe4ec | |
|
b1aa399b63 | |
|
42a732623b | |
|
db745fd309 | |
|
c7baada605 | |
|
3bd9a6922f | |
|
118fc82f2b | |
|
d09b621409 | |
|
b3cdfc1ded | |
|
f52356e93a | |
|
de2052c4d6 | |
|
5e747e7278 | |
|
f9e31efaa3 | |
|
a09b03bd2f | |
|
f219b6937c | |
|
3cea0245b3 | |
|
7abf0c82ed | |
|
bb33df53d3 | |
|
b30c1dc2ec | |
|
e481d6feaf | |
|
36c6a78455 | |
|
8d3e86e672 | |
|
3d86cdd8f4 | |
|
2982f07f55 | |
|
1f58088f3c | |
|
3841e74e1e | |
|
dda867a6ea | |
|
e9c8da021a | |
|
53c0a84f43 | |
|
0a4caad417 | |
|
8a14cf4182 | |
|
6eecf29cf7 | |
|
29c9eaa23f | |
|
c3904fbff4 | |
|
69e605e2ef | |
|
b54a46fd07 | |
|
08aed3bc40 | |
|
f20208aec7 | |
|
c61f87f4d5 | |
|
0ca907be49 | |
|
8b4e586b64 | |
|
75171b9705 | |
|
700661b664 | |
|
6a982d83fa | |
|
b572bd88cf | |
|
39b9fc159c | |
|
4c98031c3b | |
|
0ce5f9535d | |
|
58a6426fe8 | |
|
d4ee5a6930 | |
|
e71cc5358c | |
|
862e4a657a | |
|
f42020333a |
|
@ -11,4 +11,4 @@ indent_size = 4
|
|||
indent_size = 2
|
||||
|
||||
[*.java]
|
||||
max_line_length = 160
|
||||
max_line_length = 160
|
||||
|
|
|
@ -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"
|
|
@ -0,0 +1,48 @@
|
|||
name: Test and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- '[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Java ${{ matrix.java }} Test
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 8, 11, 17 ]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
- run: |
|
||||
./mvnw clean install -DskipTests -B
|
||||
./mvnw verify -B
|
||||
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
name: Deploy
|
||||
needs: test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
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: ./mvnw clean deploy -Drelease -DskipTests -B
|
||||
env:
|
||||
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
|
||||
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
|
||||
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
name: Bump version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
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:
|
||||
name: Bump Java version
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
NEW_VERSION: ${{ github.event.inputs.version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 8
|
||||
- name: Bump version using Maven
|
||||
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" {} +'
|
||||
- name: Create version bump PR
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
title: "[Release] Bump to ${{ github.event.inputs.version }}"
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "Bump to ${{ github.event.inputs.version }}"
|
||||
signoff: true
|
||||
branch: "bump/${{ github.event.inputs.version }}"
|
||||
body: >
|
||||
This PR performs the bump of the SDK to ${{ github.event.inputs.version }}.
|
||||
This PR is auto-generated by
|
||||
[create-pull-request](https://github.com/peter-evans/create-pull-request).
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
- '[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
java: [ 8, 11, 17 ]
|
||||
name: Java ${{ matrix.java }} Test
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup java
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: ${{ matrix.java }}
|
||||
distribution: 'temurin'
|
||||
- run: |
|
||||
./mvnw clean install -DskipTests -B
|
||||
./mvnw verify -B
|
||||
./mvnw javadoc:javadoc
|
|
@ -13,6 +13,8 @@ release.properties
|
|||
.classpath
|
||||
.project
|
||||
.settings/
|
||||
.vscode/
|
||||
.attach_pid*
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
@ -40,3 +42,8 @@ _site/
|
|||
.sass-cache/
|
||||
.jekyll-cache/
|
||||
.jekyll-metadata
|
||||
|
||||
# MacOS
|
||||
*.DS_Store
|
||||
/http/restful-ws-jakarta/src/main/*
|
||||
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
openssl aes-256-cbc -K $encrypted_3210c925a91b_key \
|
||||
-iv $encrypted_3210c925a91b_iv \
|
||||
-in .travis.secring.enc -out .travis.secring -d
|
||||
|
||||
gpg2 --import .travis.pubring
|
||||
|
||||
gpg2 --batch --allow-secret-key-import --import .travis.secring
|
||||
|
||||
export GPG_TTY=$(tty)
|
||||
|
||||
mvn clean deploy -Drelease -DskipTests \
|
||||
--settings .travis.settings.xml \
|
||||
-Dgpg.executable=gpg2 \
|
||||
-Dgpg.keyname=4F144A60ECA44F0D \
|
||||
-Dgpg.passphrase=$PASSPHRASE
|
|
@ -1,41 +0,0 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQGNBF2OZtgBDAC7V7wK9PBKvsPw1EMFpKyP0PAVWj1Y7V4MEWJ8zCYXLawpdYnc
|
||||
M8a5AizkAHXP6rx/fgZ+WKDbZUeQ0pyz+b7MjV7gYAsU8UZ3q1aMk5mi31lao/uw
|
||||
JRglmC5PO4QYDRrpc0fRTPxuzMzghAUfvOVfa4BZFV4K39sBgU+g9fBTlJ8e1xoW
|
||||
EoIsKR1DOvcZPU935LC0gq+auie4h+riIuiDLl2b0yiZhgJNUHhLhKaRhBERTgos
|
||||
Mkstd/bCNHBUcs0tsIzg9pTgrzvncvnwstSQR18H9Jw6tVjJQoF4HUXtJApwku3T
|
||||
lA9O3HRQZUyL7PyKvQ2UxDiL3LWv4PqeiH/MILVpANNbDMUCGTP32ru5CA19rzHf
|
||||
TTJPpVavo4vDeVoYMYI3uwxfOek/ZHmDd1ECSDBKQfE7Hwj56hpx+zez6j5G/Zzb
|
||||
4ohuwv9iu5+NWvV7prIh9GLW3esdwJPBrTDKPrLZFNc/DwrHfrc9Z2JjIbDmSJAB
|
||||
k2gujpLExBi9KlMAEQEAAbQfZmFiaW9qb3NlIDxmYWJpb2pvc2VAZ21haWwuY29t
|
||||
PokB1AQTAQgAPhYhBB80EFbYrr5VW0Ggik8USmDspE8NBQJdjmbYAhsDBQkDwmcA
|
||||
BQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEE8USmDspE8NDAUMALgvLCtkrtRe
|
||||
egNqDopgl7UoZjrfDoKojMr8l6kxIBWb/HzYBAOKSVquhdn71iRySuUSYL2BeK8f
|
||||
As144J9XkAo4cdNXvGTorOpjt2qRMdJzBONE8eVCRrW50uH7pq4YkB+8qVRzXNXz
|
||||
PGDWN0BQrJOMh12BP0JVWomABUxQsAE6NbRqu5gRcbEsLsp87AmLlVw27/J3wT/q
|
||||
Dg/LePNtBazp/LwV+j6/IkPmF08w//xQXZoKoNX7VHJ+4uIUXR8iL0SiN/2ceghu
|
||||
3r9hAUAnJf6Qewe+xlxHDCEueVR8RUvYjYbEwkRHGJlkxWyraiV+CQlyP6Vswr3N
|
||||
f87NPJTKv40pcndKiuajuNfb+6bRuOG4QMn0/GfpVrdui1iIKSVEAWIkJ+pSAlJh
|
||||
++SqqGW25M5zoXQPVfa51pzaiDa2BY9BXwrH6/jg/2GeCnCRSf53Xx/HZev/Uoro
|
||||
hCpOY2uLz+NbnprF6p9VcxhTjrl/h1ObPOosZWDCsi0BskhvLXwuPLkBjQRdjmbY
|
||||
AQwAudhK/rcocFWNDtfsPSH6zXfBEGLTHkf2hgKbVMVdNMVqxRSTieQZHbLHlA2E
|
||||
2w+ssYd9d5yQrkj+D53e9apQQretJir4X3CvTlEQ/KfWWkRH1eUNJF2FFUcvt3LR
|
||||
jRcbIy4UeJnI/boKxdHNXhuPjdx5ljPkXY7tkYHZt5jih6oiC3m4GbdsAHHAq68p
|
||||
R1IkpQnQUfYBwBzVINahStm5UcJFSXX1ZUsj8OYJOxxuObaGyiVydGOtSp7/et60
|
||||
JEi/F3Zgx//x4fNFjUTNG/2gzakM9pY24p8kS2b4o7mrNdfg6vLdMLZHjiEn4+tZ
|
||||
iI/4ve2ZKd4jLJOB20l79rpE78HrI3IJ6P5H2bRpxPNSWOVkNqcPhjpyYQLl0wU+
|
||||
zngZ8fcMvIASFWa96yG1yhr7Ly5KuTf+Ka5vOInXvZlhWe46CFr5r7waawtIrOkF
|
||||
BiXt0MIo6++4ZJdhHRpXAwctRQYj+ky7RGMGiI5GjzO1JI7IUgL4kpXidAGs4fj9
|
||||
anppABEBAAGJAbwEGAEIACYWIQQfNBBW2K6+VVtBoIpPFEpg7KRPDQUCXY5m2AIb
|
||||
DAUJA8JnAAAKCRBPFEpg7KRPDbM8DACYAP9GjOj4vazpgVX2jlQr/vbxSMp/4Qmz
|
||||
yrlTKDGfvNMot+j1NIom+CyMhYLonig90wXhacch6Ocu1RRSd/GmVyj65sTnilPr
|
||||
VSO+KmHbKDk+YBhBXoCm4pQT31/Jaw/vqz3+ZlmftFXXO96VE/IVMM8KOxmLHAao
|
||||
nmqaOOL35Uv0waUPlFjSqiGxuzPlDvBsDxpNgKQ9qUEWKKFInOAeuDJ7uONdNgOZ
|
||||
BHEMDLveLS+rkLHetMUxIuY4bqozc8jAmDOJnuTEhXUXoWLRHtgiVO3tYEYMvdRi
|
||||
bu4Oh5uJ1pVDtAOLa6cEkuVFZSRFct3SJp2PX6zN1hov+EcXf1k2W0GAnVMyWHnC
|
||||
+As9BACop5JNaG/B5johOx1IqwhPn/4IownwvKyhicT0DlLTXCy5+MYHbJi9DdIs
|
||||
L1xsu5QcGCmnH2Ur9ml61Vfb4LmlHS6n0eZ5N6mpYREKKmzvNiD47u6C5j6tDq51
|
||||
Q8rGENjZbxXtht/6AqzvOoML/Hq3xNs=
|
||||
=dors
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
Binary file not shown.
|
@ -1,28 +0,0 @@
|
|||
<!--
|
||||
~ Copyright 2018-Present The CloudEvents Authors
|
||||
~ <p>
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~ <p>
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~ <p>
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
~
|
||||
-->
|
||||
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<servers>
|
||||
<server>
|
||||
<id>ossrh</id>
|
||||
<username>${env.SONATYPE_USERNAME}</username>
|
||||
<password>${env.SONATYPE_PASSWORD}</password>
|
||||
</server>
|
||||
</servers>
|
||||
</settings>
|
46
.travis.yml
46
.travis.yml
|
@ -1,46 +0,0 @@
|
|||
language: java
|
||||
|
||||
before_install:
|
||||
- wget https://github.com/sormuras/bach/raw/master/install-jdk.sh
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- ".autoconf"
|
||||
- "$HOME/.m2"
|
||||
|
||||
env:
|
||||
global:
|
||||
- secure: P+bDfjq+RCVmZjkwqC2m1eV2jr2VbR3NA8o9BcJFDduwKHboflzp92sL/Risp9QGwdRomPIfzUBL8Dq13lgDSgflfkZpbLhRamXi06E74MOHIZeV3EYbhOTmWa7DbBueUCTVmxmfgNI+207EtlKkW9ZBsqky/tUuLXdFCjjonDN3f3PmRI7odxZ4r5di46h73yynh+8+pAx7T6K52PJNGXtAZflKU2NHnfAUainAHMOY3UAOfL0vSVhKvW7s6i9/qrqUAXgN0PUeiAwKi0RnuoTEAe8n+GILYH8xRkkkTLZj3uxwI9lTYdtN2wOCJZ8U7bmy8jQxFCaes/LgyfL5F4e4r3FUPrFpgPOBqiP+7T20aoCxbMFQ2EUkRbwXsNXEQu1FCBNH/sMdw7FEfOfE43jIe4yQIoWlmz+Su0RGqgwOiolMb0O10M31apKqKmmyPBb9c9AWwraKZ1FRe+tTVVRRBlWUAxYjLhha4uczW3oY+sE2yY8k5gPN5JNWqvy92UZUupfAKoxh21C2K5ygeQR7crEnwwtw1flu9QUWfFYBvUKK7aC33WwRq+4RKB3oPeD9b/+Ix5jUcad9wqy5rw0rKJRHJzvYoAaJ+kIlnD9nZLq52fj5gs/+1JOlRpE3rP0z9M6qUPE5QWHa8DGYvXwe8s93gZbcBPqxVNv1U6o=
|
||||
- secure: UFn72lygDZrU66+kJJ7tH2MiGvq2sfUu396TaVkZipVz7wW0NZ//wkI1w/SKluPjEa/dJ3hcxz+IR8R9QCcZgIbBhaxgmjCG10majm24+3TqcqeE/lUHtD00LGDJUmSbr2vsenHYRZk7TNe3Cb3YrKL2APSTVaMWJo3fCMT7BaydXuAL8D4ZAJrPTw9S9GsyjAGaPhN8P8wsJCBhRhOxS+TuMcjLTI6fXIN5kdZz6IygpRhmx7RSdICAl+otHz7znUQh4PyfAPGPItwACXTcDfF4lQ/DUNfA8FcRnR76lgvMSIS291YLy22ToBe5LT5smQWT4VE3TDVhkn9uAyTGXBN1NlOsiqKxW8eyFUckU0rQIkHHz6FFAtVnklt4EArN1l+KJksl524kBTph/IKWONiTNGKUwc4fIzB8o/iMIa0ZtadR0Fde32ssiEk7+lvkEHypZUe/WF+mEi8Y4PUeM9OBP/Gdxb7hLLzCaJNfZ3NRGYRZJieVk608jnRi7XADSRnw830ga5A3YbkZo7ljAD2UN3BKwhffs1FEnmGBz2mdnPie/HJ0nTf7g7/vIxgZT7UGcZ35olFah2kUEbiZkbT6OmKBY0a5regUB7+6aTjcP5aPFLkacz5BIs1jvrjcsalYEi+KFKWfmeLVQurqEQSpO0ZOVS7cAfm4qGTkv8Y=
|
||||
- secure: Fl7zB+DmZTER2j1PfHUlJORWCkqSWPViwqPeV+OvHK59Oisgv+MJ7w5jU+N5iEmKi7V6moDCpdnChAlLN4XxYNKWHhuw1jv1GAvr083qpTRikbHvzPGNGU1g3g5tResslYJRW+U8pbhl+i50I7i0+VbYshHDUvtKiC6Wfb4KSDkIGNPhekmw1RZ/tZkZPwgX95sAqioyeI/AGAlI7x209eIHL6igxt5auc4XsfqTU9fjIoyyp1jRP4WRJ3tatJshAiwM+9SI0vzZyIe7w3d6y9CtEGxcBlCcCYn18YJ1+7da3Bfxv4nrIfsRKuVW/p2cypc7N26WMgsVlUzXryX/Bqie4SvFBiEM+WJey8YW2wc8Fr0ndcFjPFIsvpo+uHQRkwzuGspbzmfIfOJVVKCbIhAuKQZSGuJB5bSKtQxk0Vgz35Q4ag8gr8UfrjjoMtP0bb4N7nzYTHB/osDDKT9gmM/lBWyjXPRtCHWDgPJxSWrLxoXVWnzGZC/6Jc0eeJQlVOZ3YUViqSV2x3cXlOjyzSCkoDGk7fMFgPL+YP1gvn47KMhN9MtrSY+vkGqwDWrXb+0Mq4z7EIog5ZD+qE5XB014lDnQEk7GVpp5DVktdMBXkCBxpmBPDnpPF2BuTgf7nAMtJcNgq0PpLYc2d4W9ZpB0rD5KO1G83/ai+KnCWig=
|
||||
|
||||
jobs:
|
||||
fast_finish: true
|
||||
include:
|
||||
# OpenJDK
|
||||
- stage: test
|
||||
name: "OpenJDK 8"
|
||||
jdk: openjdk8
|
||||
script: mvn clean verify -B
|
||||
- name: "OpenJDK 11"
|
||||
jdk: openjdk11
|
||||
script: mvn clean verify -B
|
||||
# GraalVM
|
||||
- name: "GraalVM 20.0.0 (8)"
|
||||
if: type != pull_request
|
||||
install: . ./install-jdk.sh --url "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.0.0/graalvm-ce-java8-linux-amd64-20.0.0.tar.gz"
|
||||
script: mvn clean verify -B
|
||||
- name: "GraalVM 20.0.0 (11)"
|
||||
if: type != pull_request
|
||||
install: . ./install-jdk.sh --url "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-20.0.0/graalvm-ce-java11-linux-amd64-20.0.0.tar.gz"
|
||||
script: mvn clean verify -B
|
||||
- stage: deploy
|
||||
if: type != pull_request
|
||||
name: Deploy to Maven Repository
|
||||
jdk: openjdk8
|
||||
script: skip
|
||||
deploy:
|
||||
provider: script
|
||||
script: bash .travis.deploy.sh
|
||||
on:
|
||||
branch: master
|
100
CHANGELOG.md
100
CHANGELOG.md
|
@ -1,100 +0,0 @@
|
|||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.3.0]
|
||||
|
||||
### Added
|
||||
|
||||
- [Editor configuration file](./.editorconfig) - [check it out!](https://editorconfig.org/)
|
||||
|
||||
### Fixed
|
||||
|
||||
- [Map extensions with prefix for Kafka CloudEvent header](https://github.com/cloudevents/sdk-java/pull/98)
|
||||
- [Add new CloudEventsKafkaHeaders class for kafka API](https://github.com/cloudevents/sdk-java/pull/90)
|
||||
- [Fix Kafka Transport references](https://github.com/cloudevents/sdk-java/pull/78)
|
||||
|
||||
## [1.2.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Improved base64 marshalling: PR [#79](https://github.com/cloudevents/sdk-java/pull/79)
|
||||
|
||||
## [1.1.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Support for custom validator: PR [#73](https://github.com/cloudevents/sdk-java/pull/73)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix for the issue [#74](https://github.com/cloudevents/sdk-java/issues/74)
|
||||
- Fix for the issue [#67](https://github.com/cloudevents/sdk-java/issues/67)
|
||||
|
||||
## [1.0.0]
|
||||
|
||||
### Added
|
||||
|
||||
- Support for [Spec v1.0](https://github.com/cloudevents/spec/tree/v1.0)
|
||||
- Improved readme to end request for vertx sample [PR 62](https://github.com/cloudevents/sdk-java/pull/62)
|
||||
- Allow providing an external validator [PR 65](https://github.com/cloudevents/sdk-java/pull/65)
|
||||
|
||||
## [0.3.1]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Vulnerable dependency `com.fasterxml.jackson.core:jackson-databind`
|
||||
|
||||
## [0.3.0]
|
||||
|
||||
### Added
|
||||
- [Attributes](./api/src/main/java/io/cloudevents/Attributes.java) marker
|
||||
interface for context attributes
|
||||
- Support for [Spec v0.3](https://github.com/cloudevents/spec/tree/v0.3)
|
||||
- [ExtensionFormat](./api/src/main/java/io/cloudevents/ExtensionFormat.java)
|
||||
interface for extensions
|
||||
- [HTTP Marshallers](./api/src/main/java/io/cloudevents/v02/http/Marshallers.java) with bare minium marshallers for HTTP Transport Binding
|
||||
- [HTTP Unmarshallers](./api/src/main/java/io/cloudevents/v02/http/Unmarshallers.java) with bare minium unmarshallers for HTTP Transport Binding
|
||||
- [Kafka Marshallers](./kafka/src/main/java/io/cloudevents/v02/kafka/Marshallers.java) with bare minimum marshallers for Kafka Transport Binding
|
||||
- [CloudEventsKafkaProducer](./kafka/src/main/java//io/cloudevents/kafka/CloudEventsKafkaProducer.java) The CloudEvents producer that uses Kafka Clients API
|
||||
- [Kafka Unmarshallers](./kafka/src/main/java/io/cloudevents/v02/kafka/Unmarshallers.java) with bare minium unmarshallers for Kafka Transport Binding
|
||||
- [CloudEventsKafkaConsumer](./kafka/src/main/java//io/cloudevents/kafka/CloudEventsKafkaConsumer.java) The CloudEvents consumer that uses Kafka Clients API
|
||||
- [BinaryMarshaller](./api/src/main/java/io/cloudevents/format/BinaryMarshaller.java) To help in the case of you need to create your own marshallers for binary content mode
|
||||
- [BinaryUnmarshaller](./api/src/main/java/io/cloudevents/format/BinaryUnmarshaller.java) To help in the case of you need to create your own unmarshallers for binary content mode
|
||||
- [StructuredMarshaller](./api/src/main/java/io/cloudevents/format/StructuredMarshaller.java) To help in the case of you need to create your own marshallers for structured content mode
|
||||
- [StructuredUnmarshaller](./api/src/main/java/io/cloudevents/format/StructuredUnmarshaller.java) To help in the case of you need to create your own unmarshallers for structured content mode
|
||||
|
||||
|
||||
### Changed
|
||||
- CloudEvent interface signature, breaking the backward compatibility
|
||||
- Class: `io.cloudevents.v02.CloudEvent` moved to `io.cloudevents.v02.CloudEventImpl`
|
||||
- Class: `io.cloudevents.v02.CloudEventImpl` had changed its signature, breaking the
|
||||
backward compatibility
|
||||
- Method: `io.cloudevents.json.Json.fromInputStream` had moved to Generics signature
|
||||
- Class: `io.cloudevents.http.V02HttpTransportMappers` moved to
|
||||
`io.cloudevents.v02.http.BinaryAttributeMapperImpl` and had changed is signature breaking the backward compatibility
|
||||
|
||||
### Removed
|
||||
- Support for Spec v0.1
|
||||
- Class: `io.cloudevents.impl.DefaultCloudEventImpl`, in favor of a impl for each
|
||||
version
|
||||
- Class: `io.cloudevents.CloudEventBuilder`, in favor of a builder for each version
|
||||
- Enum: `io.cloudevents.SpecVersion`, in favor of specialization of specs
|
||||
- Method: `io.cloudevents.json.Json.decodeCloudEvent`
|
||||
- Class: `io.cloudevents.http.V01HttpTransportMappers` due the unsupported v0.1
|
||||
- interface: `io.cloudevents.http.HttpTransportAttributes`, in favor of the new
|
||||
abstract envelope signature
|
||||
- interface: `io.cloudevents.Extension` in favor of
|
||||
`io.cloudevents.extensions.ExtensionFormat`
|
||||
|
||||
[Unreleased]: https://github.com/cloudevents/sdk-java/compare/v1.3.0...HEAD
|
||||
[1.3.0]: https://github.com/cloudevents/sdk-java/compare/v1.2.0...v1.3.0
|
||||
[1.2.0]: https://github.com/cloudevents/sdk-java/compare/v1.1.0...v1.2.0
|
||||
[1.1.0]: https://github.com/cloudevents/sdk-java/compare/v1.0.0...v1.1.0
|
||||
[1.0.0]: https://github.com/cloudevents/sdk-java/compare/v0.3.1...v1.0.0
|
||||
[0.3.1]: https://github.com/cloudevents/sdk-java/compare/v0.3.0...v0.3.1
|
||||
[0.3.0]: https://github.com/cloudevents/sdk-java/compare/v0.2.1...v0.3.0
|
|
@ -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)
|
|
@ -26,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.
|
139
README.md
139
README.md
|
@ -1,66 +1,123 @@
|
|||
# Java SDK for CloudEvents API
|
||||
|
||||
[](https://travis-ci.org/cloudevents/sdk-java)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://maven-badges.herokuapp.com/maven-central/io.cloudevents/cloudevents-parent)
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
|
||||
A Java API for the [CloudEvents specification](https://github.com/cloudevents/spec)
|
||||
The Java SDK for CloudEvents is a collection of Java packages to adopt
|
||||
[CloudEvents](https://github.com/cloudevents/spec) in your Java application.
|
||||
|
||||
Look at https://cloudevents.github.io/sdk-java/ for more documentation.
|
||||
Using the Java SDK you can:
|
||||
|
||||
__Checkout the [changelog](./CHANGELOG.md)__
|
||||
- Access, create and manipulate `CloudEvent` inside your application.
|
||||
- Serialize and deserialize `CloudEvent` back and forth using the _CloudEvents
|
||||
Event Format_, like Json.
|
||||
- Read and write `CloudEvent` back and forth to HTTP, Kafka, AMQP using the
|
||||
_CloudEvents Protocol Binding_ implementations we provide for a wide range
|
||||
of well known Java frameworks/libraries.
|
||||
|
||||
To check out the complete documentation and how to get started, look at the dedicated website
|
||||
https://cloudevents.github.io/sdk-java/.
|
||||
|
||||
## Status
|
||||
|
||||
This SDK is considered **work in progress**. The community is working hard to bring you a new major version of the SDK with major enhancements both to APIs and to implementation.
|
||||
This SDK is considered **work in progress**. The community is working hard to
|
||||
bring you a new major version of the SDK with major enhancements both to APIs
|
||||
and to implementation.
|
||||
|
||||
If you want to know more about v1 of this SDK, check out the [v1 readme](./README_v1.md)
|
||||
If you want to know more about v1 of this SDK, check out the
|
||||
[v1 readme](https://github.com/cloudevents/sdk-java/tree/1.x)
|
||||
|
||||
Stay tuned!
|
||||
|
||||
Supported features of the specification:
|
||||
|
||||
| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) |
|
||||
| :---------------------------: | :----------------------------------------------------------------------------: | :---------------------------------------------------------------------------------: |
|
||||
| CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| AMQP Protocol Binding | :x: | :x: |
|
||||
| AVRO Event Format | :x: | :x: |
|
||||
| HTTP Protocol Binding | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Vert.x](http/vertx) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jakarta Restful WS](http/restful-ws) | :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: |
|
||||
| [Kafka Protocol Binding](kafka) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
| | [v0.3](https://github.com/cloudevents/spec/tree/v0.3) | [v1.0](https://github.com/cloudevents/spec/tree/v1.0) |
|
||||
| :-------------------------------------: | :---------------------------------------------------: | :---------------------------------------------------: |
|
||||
| CloudEvents Core | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| AMQP Protocol Binding | :x: | :x: |
|
||||
| - [Proton](amqp) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| AVRO Event Format | :x: | :x: |
|
||||
| HTTP Protocol Binding | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Vert.x](http/vertx) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jakarta Restful WS](http/restful-ws) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Basic](http/basic) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Spring](spring) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [http4k][http4k]<sup>†</sup>| :heavy_check_mark: | :heavy_check_mark: |
|
||||
| JSON Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Jackson](formats/json-jackson) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| Protobuf Event Format | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| - [Proto](formats/protobuf) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| [Kafka Protocol Binding](kafka) | :heavy_check_mark: | :heavy_check_mark: |
|
||||
| MQTT Protocol Binding | :x: | :x: |
|
||||
| NATS Protocol Binding | :x: | :x: |
|
||||
| Web hook | :x: | :x: |
|
||||
|
||||
## Motivation
|
||||
<sub>† Source/artifacts hosted externally</sub>
|
||||
|
||||
The [CloudEvents specification](https://github.com/cloudevents/spec) is a vendor-neutral specification for defining the format of event data that is being exchanged between different cloud systems. The specification basically defines an abstract envelope for any event data payload, without knowing specific implementation details of the actual underlying event. The current version of the spec is at `0.3` and it describes a simple event format, which was demonstrated at [KubeCon 2018](https://youtu.be/TZPPjAv12KU) using different _Serverless platforms_, such as [Apache Openwhisk](https://github.com/apache/incubator-openwhisk).
|
||||
## Documentation
|
||||
|
||||
## Java API
|
||||
Documentation is available at https://cloudevents.github.io/sdk-java/.
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Java SDK:
|
||||
Javadocs are available on [javadoc.io](https://www.javadoc.io):
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>2.0.0-milestone1</version>
|
||||
</dependency>
|
||||
```
|
||||
- [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
- [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
- [cloudevents-avro-compact](https://www.javadoc.io/doc/io.cloudevents/cloudevents-avro-compact)
|
||||
- [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
|
||||
- [cloudevents-protobuf](https://www.javadoc.io/doc/io.cloudevents/cloudevents-protobuf)
|
||||
- [cloudevents-xml](https://www.javadoc.io/doc/io.cloudevents/cloudevents-xml)
|
||||
- [cloudevents-http-basic](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-basic)
|
||||
- [cloudevents-http-restful-ws](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-restful-ws)
|
||||
- [cloudevents-http-vertx](https://www.javadoc.io/doc/io.cloudevents/cloudevents-http-vertx)
|
||||
- [cloudevents-kafka](https://www.javadoc.io/doc/io.cloudevents/cloudevents-kafka)
|
||||
- [cloudevents-amqp](https://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp)
|
||||
- [cloudevents-spring](https://www.javadoc.io/doc/io.cloudevents/cloudevents-spring)
|
||||
|
||||
You can check out the examples in the [examples](examples) directory.
|
||||
|
||||
## Used By
|
||||
|
||||
| [Occurrent](https://occurrent.org) | [Knative Eventing](https://github.com/knative-sandbox/eventing-kafka-broker )| [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
|
||||
|
||||
- There are bi-weekly calls immediately following the [Serverless/CloudEvents
|
||||
call](https://github.com/cloudevents/spec#meeting-time) at
|
||||
9am PT (US Pacific). Which means they will typically start at 10am PT, but
|
||||
if the other call ends early then the SDK call will start early as well.
|
||||
See the [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#)
|
||||
to determine which week will have the call.
|
||||
- Slack: #cloudeventssdk channel under
|
||||
[CNCF's Slack workspace](https://slack.cncf.io/).
|
||||
- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
|
||||
- Contact for additional information: Fabio José (`@fabiojose` on slack).
|
||||
- There are bi-weekly calls immediately following the
|
||||
[Serverless/CloudEvents call](https://github.com/cloudevents/spec#meeting-time)
|
||||
at 9am PT (US Pacific). Which means they will typically start at 10am PT,
|
||||
but if the other call ends early then the SDK call will start early as well.
|
||||
See the
|
||||
[CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#)
|
||||
to determine which week will have the call.
|
||||
- Slack: #cloudeventssdk channel under
|
||||
[CNCF's Slack workspace](https://slack.cncf.io/).
|
||||
- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
|
||||
- Contact for additional information: Francesco Guardiani (`@slinkydeveloper`
|
||||
on slack), Fabio José (`@fabiojose` on slack).
|
||||
|
||||
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/main/docs)
|
||||
directory. In particular, in there you will find information concerning how SDK
|
||||
projects are
|
||||
[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/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/
|
||||
|
|
126
README_v1.md
126
README_v1.md
|
@ -1,126 +0,0 @@
|
|||
# Java SDK for CloudEvents API
|
||||
|
||||
[](https://travis-ci.org/cloudevents/sdk-java)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://maven-badges.herokuapp.com/maven-central/io.cloudevents/cloudevents-parent)
|
||||
[](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
|
||||
A Java API for the [CloudEvents specification](https://github.com/cloudevents/spec)
|
||||
|
||||
__Checkout the [changelog](./CHANGELOG.md)__
|
||||
|
||||
## Status
|
||||
|
||||
This SDK current supports the following versions of CloudEvents:
|
||||
|
||||
- 0.2
|
||||
- 0.3
|
||||
- 1.0
|
||||
|
||||
## Motivation
|
||||
|
||||
The [CloudEvents specification](https://github.com/cloudevents/spec) is a vendor-neutral specification for defining the format of event data that is being exchanged between different cloud systems. The specification basically defines an abstract envelope for any event data payload, without knowing specific implementation details of the actual underlying event. The current version of the spec is at `0.3` and it describes a simple event format, which was demonstrated at [KubeCon 2018](https://youtu.be/TZPPjAv12KU) using different _Serverless platforms_, such as [Apache Openwhisk](https://github.com/apache/incubator-openwhisk).
|
||||
|
||||
## Java API
|
||||
|
||||
For Maven based projects, use the following to configure the CloudEvents Java SDK:
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>1.3.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Application developers can now create strongly-typed CloudEvents, such as:
|
||||
|
||||
```java
|
||||
import io.cloudevents.v1.CloudEventBuilder;
|
||||
import io.cloudevents.v1.CloudEventImpl;
|
||||
import io.cloudevents.extensions.ExtensionFormat;
|
||||
import io.cloudevents.json.Json;
|
||||
import io.cloudevents.extensions.DistributedTracingExtension;
|
||||
|
||||
// given
|
||||
final String eventId = UUID.randomUUID().toString();
|
||||
final URI src = URI.create("/trigger");
|
||||
final String eventType = "My.Cloud.Event.Type";
|
||||
final MyCustomEvent payload = ...
|
||||
|
||||
// add trace extension usin the in-memory format
|
||||
final DistributedTracingExtension dt = new DistributedTracingExtension();
|
||||
dt.setTraceparent("00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
|
||||
dt.setTracestate("rojo=00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01");
|
||||
|
||||
final ExtensionFormat tracing = new DistributedTracingExtension.Format(dt);
|
||||
|
||||
// passing in the given attributes
|
||||
final CloudEventImpl<MyCustomEvent> cloudEvent =
|
||||
CloudEventBuilder.<MyCustomEvent>builder()
|
||||
.withType(eventType)
|
||||
.withId(eventId)
|
||||
.withSource(src)
|
||||
.withData(payload)
|
||||
.withExtension(tracing)
|
||||
.build();
|
||||
|
||||
// marshalling as json
|
||||
final String json = Json.encode(cloudEvent);
|
||||
```
|
||||
|
||||
Or, an event with binary event data:
|
||||
|
||||
```java
|
||||
import io.cloudevents.v1.CloudEventBuilder;
|
||||
import io.cloudevents.v1.CloudEventImpl;
|
||||
import io.cloudevents.extensions.ExtensionFormat;
|
||||
import io.cloudevents.json.Json;
|
||||
import io.cloudevents.extensions.DistributedTracingExtension;
|
||||
|
||||
// given
|
||||
final String eventId = UUID.randomUUID().toString();
|
||||
final URI src = URI.create("/trigger");
|
||||
final String eventType = "My.Cloud.Event.Type";
|
||||
final byte[] payload = "a-binary-event-data".getBytes();
|
||||
|
||||
// passing in the given attributes
|
||||
final CloudEventImpl<String> cloudEvent =
|
||||
CloudEventBuilder.<String>builder()
|
||||
.withType(eventType)
|
||||
.withId(eventId)
|
||||
.withSource(src)
|
||||
.withDataBase64(payload)
|
||||
.build();
|
||||
|
||||
// marshalling as json that will have the data_base64
|
||||
final String json = Json.encode(cloudEvent);
|
||||
```
|
||||
|
||||
There are [other detailed ways](./api/README.md) of how to use the marshallers
|
||||
and unmarshallers with HTTP transport binding.
|
||||
|
||||
## Kafka
|
||||
|
||||
The support for kafka protocol binding is available. Read the
|
||||
[documentation and examples](./kafka/README.md) of use.
|
||||
|
||||
## Possible Integrations
|
||||
|
||||
The API is kept simple, for allowing a wide range of possible integrations:
|
||||
|
||||
* [CDI](cdi/)
|
||||
* [Eclipse Vert.x](http/vertx/)
|
||||
|
||||
## Community
|
||||
|
||||
- There are bi-weekly calls immediately following the [Serverless/CloudEvents
|
||||
call](https://github.com/cloudevents/spec#meeting-time) at
|
||||
9am PT (US Pacific). Which means they will typically start at 10am PT, but
|
||||
if the other call ends early then the SDK call will start early as well.
|
||||
See the [CloudEvents meeting minutes](https://docs.google.com/document/d/1OVF68rpuPK5shIHILK9JOqlZBbfe91RNzQ7u_P7YCDE/edit#)
|
||||
to determine which week will have the call.
|
||||
- Slack: #cloudeventssdk channel under
|
||||
[CNCF's Slack workspace](https://slack.cncf.io/).
|
||||
- Email: https://lists.cncf.io/g/cncf-cloudevents-sdk
|
||||
- Contact for additional information: Fabio José (`@fabiojose` on slack).
|
|
@ -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.
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
# AMQP Protocol Binding
|
||||
|
||||
Javadoc: [](http://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp-proton)
|
||||
|
||||
Documentation: https://cloudevents.github.io/sdk-java/amqp-proton
|
|
@ -0,0 +1,64 @@
|
|||
<?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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<name>CloudEvents - Proton AMQP Binding</name>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<protonj.version>0.34.1</protonj.version>
|
||||
<jsr305.version>3.0.2</jsr305.version>
|
||||
<module-name>io.cloudevents.amqp.proton</module-name>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.qpid</groupId>
|
||||
<artifactId>proton-j</artifactId>
|
||||
<version>${protonj.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>${jsr305.version}</version>
|
||||
<scope>provided</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<classifier>tests</classifier>
|
||||
<type>test-jar</type>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj-core.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
* 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.amqp;
|
||||
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.amqp.impl.AmqpConstants;
|
||||
import io.cloudevents.amqp.impl.ProtonAmqpBinaryMessageReader;
|
||||
import io.cloudevents.amqp.impl.ProtonAmqpMessageWriter;
|
||||
import io.cloudevents.core.message.MessageReader;
|
||||
import io.cloudevents.core.message.MessageWriter;
|
||||
import io.cloudevents.core.message.impl.GenericStructuredMessageReader;
|
||||
import io.cloudevents.core.message.impl.MessageUtils;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Section;
|
||||
import org.apache.qpid.proton.message.Message;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* A factory class providing convenience methods for creating {@link MessageReader} and {@link MessageWriter} instances based on Qpid Proton {@link Message}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class ProtonAmqpMessageFactory {
|
||||
|
||||
private ProtonAmqpMessageFactory() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MessageReader} to read a proton-based {@link Message}.
|
||||
* This reader is able to read both binary and structured encoded {@link io.cloudevents.CloudEvent}.
|
||||
*
|
||||
* @param message The proton {@link Message} to read from.
|
||||
* @return A {@link MessageReader} that can read the given proton {@link Message} to a {@link io.cloudevents.CloudEvent} representation.
|
||||
* @throws CloudEventRWException if something goes wrong while resolving the {@link SpecVersion} or if the message has unknown encoding
|
||||
* @see #createReader(String, ApplicationProperties, Section)
|
||||
*/
|
||||
public static MessageReader createReader(final Message message) throws CloudEventRWException {
|
||||
return createReader(message.getContentType(), message.getApplicationProperties(), message.getBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a MessageReader to read using the {@code content-type} property, {@code application-properties} and data payload
|
||||
* of a proton-based {@link Message}. This reader is able to read both binary and structured encoded {@link io.cloudevents.CloudEvent}.
|
||||
*
|
||||
* @param contentType The {@code content-type} of the message payload.
|
||||
* @param props The {@code application-properties} section of the proton-message containing cloud event metadata (attributes and/or extensions).
|
||||
* @param body The message body or {@code null} if the message does not contain any body.
|
||||
* @return A {@link MessageReader} capable of representing a {@link io.cloudevents.CloudEvent} from the {@code application-properties},
|
||||
* {@code content-type} and payload of a proton message.
|
||||
* @throws CloudEventRWException if something goes wrong while resolving the {@link SpecVersion} or if the message has unknown encoding
|
||||
*/
|
||||
public static MessageReader createReader(final String contentType, final ApplicationProperties props, @Nullable final Section body) throws CloudEventRWException {
|
||||
final byte[] payload = AmqpConstants.getPayloadAsByteArray(body);
|
||||
return MessageUtils.parseStructuredOrBinaryMessage(
|
||||
() -> contentType,
|
||||
format -> new GenericStructuredMessageReader(format, payload),
|
||||
() -> AmqpConstants.getApplicationProperty(props, AmqpConstants.APP_PROPERTY_SPEC_VERSION, String.class),
|
||||
sv -> new ProtonAmqpBinaryMessageReader(sv, props, contentType, payload)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MessageWriter} capable of translating both a structured and binary CloudEvent
|
||||
* to a proton-based AMQP 1.0 {@link Message}.
|
||||
*
|
||||
* @return A {@link MessageWriter} to write a {@link io.cloudevents.CloudEvent} to Proton {@link Message} using structured or binary encoding.
|
||||
*/
|
||||
public static MessageWriter<CloudEventWriter<Message>, Message> createWriter() {
|
||||
return new ProtonAmqpMessageWriter<>();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* 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.amqp.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Data;
|
||||
import org.apache.qpid.proton.amqp.messaging.Section;
|
||||
|
||||
import io.cloudevents.core.message.impl.MessageUtils;
|
||||
|
||||
/**
|
||||
* Constants and methods used throughout the Proton-based implementation of the AMQP 1.0 protocol
|
||||
* binding for cloud events.
|
||||
*/
|
||||
public final class AmqpConstants {
|
||||
|
||||
private AmqpConstants() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* The prefix name for CloudEvent attributes for use in the <em>application-properties</em> section
|
||||
* of an AMQP 1.0 message.
|
||||
*/
|
||||
public static final String CE_PREFIX = "cloudEvents:";
|
||||
|
||||
/**
|
||||
* The AMQP 1.0 <em>content-type</em> message property
|
||||
*/
|
||||
public static final String PROPERTY_CONTENT_TYPE = "content-type";
|
||||
|
||||
/**
|
||||
* Map a cloud event attribute name to a value. All values except the <em>datacontenttype</em> attribute are prefixed
|
||||
* with "cloudEvents:" as mandated by the spec.
|
||||
*/
|
||||
public static final Map<String, String> ATTRIBUTES_TO_PROPERTYNAMES = MessageUtils.generateAttributesToHeadersMapping(CEA -> {
|
||||
if (CEA.equals("datacontenttype")) {
|
||||
return PROPERTY_CONTENT_TYPE;
|
||||
}
|
||||
// prefix the attribute
|
||||
return CE_PREFIX + CEA;
|
||||
});
|
||||
|
||||
public static final String APP_PROPERTY_SPEC_VERSION = ATTRIBUTES_TO_PROPERTYNAMES.get("specversion");
|
||||
|
||||
/**
|
||||
* Gets the value of a specific <em>application property</em>.
|
||||
*
|
||||
* @param <T> The expected type of the property to retrieve.
|
||||
* @param props The application properties to retrieve the value from.
|
||||
* @param name The name of the application property.
|
||||
* @param type The expected value type.
|
||||
* @return The value or {@code null} if the properties do not contain a value of the expected type for the given
|
||||
* name.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getApplicationProperty(final ApplicationProperties props, final String name,
|
||||
final Class<T> type) {
|
||||
|
||||
if (props == null) {
|
||||
return null;
|
||||
} else {
|
||||
final Object value = props.getValue().get(name);
|
||||
if (type.isInstance(value)) {
|
||||
return (T) value;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a message payload into a byte array.
|
||||
* <p>
|
||||
* The bytes in the array are determined as follows:
|
||||
* <ul>
|
||||
* <li>If the body is a Data section, the bytes contained in the
|
||||
* Data section are returned.</li>
|
||||
* <li>If the body is an AmqpValue section and contains a byte array,
|
||||
* the bytes in the array are returned.</li>
|
||||
* <li>If the body is an AmqpValue section and contains a non-empty String,
|
||||
* the UTF-8 encoding of the String is returned.</li>
|
||||
* <li>In all other cases, {@code null} is returned.</li>
|
||||
* </ul>
|
||||
|
||||
* @param payload The message payload to extract the bytes from.
|
||||
* @return The payload bytes or {@code null} if the above stated conditions are not met.
|
||||
*/
|
||||
public static byte[] getPayloadAsByteArray(final Section payload) {
|
||||
if (payload == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (payload instanceof Data) {
|
||||
final Data body = (Data) payload;
|
||||
return body.getValue().getArray();
|
||||
} else if (payload instanceof AmqpValue) {
|
||||
final AmqpValue body = (AmqpValue) payload;
|
||||
if (body.getValue() instanceof byte[]) {
|
||||
return (byte[]) body.getValue();
|
||||
} else if (body.getValue() instanceof String &&
|
||||
((String) body.getValue()).length() > 0 ) {
|
||||
return ((String) body.getValue()).getBytes(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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.amqp.impl;
|
||||
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.data.BytesCloudEventData;
|
||||
import io.cloudevents.core.message.impl.BaseGenericBinaryMessageReaderImpl;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* An AMQP 1.0 message reader that can be read as a <em>CloudEvent</em>.
|
||||
* <p>
|
||||
*
|
||||
* This reader reads sections of an AMQP message to construct a CloudEvent representation by doing the following:
|
||||
* <ul>
|
||||
* <li> If the content-type property is set for an AMQP message, the value of the property
|
||||
* is represented as a cloud event datacontenttype attribute.
|
||||
* <li> If the (mandatory) application-properties of the AMQP message contains attributes and/or extentions,
|
||||
* this reader will represent each property/extension as a cloud event attribute.
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
public final class ProtonAmqpBinaryMessageReader extends BaseGenericBinaryMessageReaderImpl<String, Object> {
|
||||
|
||||
private final String contentType;
|
||||
private final ApplicationProperties applicationProperties;
|
||||
|
||||
/**
|
||||
* Create an instance of an AMQP message reader.
|
||||
*
|
||||
* @param version The version of the cloud event message.
|
||||
* @param applicationProperties The application properties of the AMQP message that contains
|
||||
* the cloud event metadata (i.e attributes and extensions).
|
||||
* The applicationProperties MUST not be {@code null}.
|
||||
* @param contentType The content-type property of the AMQP message or {@code null} if the message content type is unknown.
|
||||
* @param payload The message payload or {@code null} if the message does not contain any payload.
|
||||
*
|
||||
* @throws NullPointerException if the applicationPropereties is {@code null}.
|
||||
*/
|
||||
public ProtonAmqpBinaryMessageReader(final SpecVersion version, final ApplicationProperties applicationProperties,
|
||||
final String contentType, final byte[] payload) {
|
||||
super(version, payload != null && payload.length > 0 ? BytesCloudEventData.wrap(payload) : null);
|
||||
this.contentType = contentType;
|
||||
this.applicationProperties = Objects.requireNonNull(applicationProperties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isContentTypeHeader(final String key) {
|
||||
return key.equals(AmqpConstants.PROPERTY_CONTENT_TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether the given attribute key is prefixed with <em>cloudEvents:</em>
|
||||
*
|
||||
* @param key The key to test for the presence of the prefix.
|
||||
* @return True if the specified key starts with the prefix or
|
||||
* false otherwise.
|
||||
*/
|
||||
@Override
|
||||
protected boolean isCloudEventsHeader(final String key) {
|
||||
final int prefixLength = AmqpConstants.CE_PREFIX.length();
|
||||
return key.length() > prefixLength && key.startsWith(AmqpConstants.CE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cloud event attribute key without the preceding prefix.
|
||||
*
|
||||
* @param key The key containing the AMQP specific prefix.
|
||||
*
|
||||
* @return The key without the prefix.
|
||||
*/
|
||||
@Override
|
||||
protected String toCloudEventsKey(final String key) {
|
||||
return key.substring(AmqpConstants.CE_PREFIX.length());
|
||||
}
|
||||
|
||||
/**
|
||||
* Visits the <em>content-type</em> message property and all <em>application-properties</em> of this message reader.
|
||||
* <p>
|
||||
* This method only visits properties containing a name and value which are not {@code null}.
|
||||
*
|
||||
* @param fn A callback to consume this reader's application-properties
|
||||
* and content-type property.
|
||||
*/
|
||||
@Override
|
||||
protected void forEachHeader(final BiConsumer<String, Object> fn) {
|
||||
if (contentType != null) {
|
||||
// visit the content-type message property
|
||||
fn.accept(AmqpConstants.PROPERTY_CONTENT_TYPE, contentType);
|
||||
}
|
||||
// visit application-properties
|
||||
applicationProperties.getValue().forEach((k, v) -> {
|
||||
if (k != null && v != null) {
|
||||
fn.accept(k, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cloud event representation of the value.
|
||||
* <p>
|
||||
* This method simply returns the string representation of the type of value passed as argument.
|
||||
*
|
||||
* @param value The value of a CloudEvent attribute or extension.
|
||||
*
|
||||
* @return The string representation of the specified value.
|
||||
*/
|
||||
@Override
|
||||
protected String toCloudEventsValue(final Object value) {
|
||||
return value.toString();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* 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.amqp.impl;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.core.message.MessageWriter;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import org.apache.qpid.proton.amqp.Binary;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Data;
|
||||
import org.apache.qpid.proton.message.Message;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* A proton-based MessageWriter capable of writing both structured and binary CloudEvent messages to an AMQP 1.0 representation as
|
||||
* mandated by the AMQP 1.0 protocol binding specification for cloud events.
|
||||
* <p>
|
||||
* This writer returns an AMQP message at the end of the write process.
|
||||
*/
|
||||
public final class ProtonAmqpMessageWriter<R> implements MessageWriter<CloudEventWriter<Message>, Message>, CloudEventWriter<Message> {
|
||||
|
||||
private ApplicationProperties applicationProperties;
|
||||
private Message message;
|
||||
|
||||
/**
|
||||
* Creates a proton-base message writer.
|
||||
*/
|
||||
public ProtonAmqpMessageWriter() {
|
||||
message = Message.Factory.create();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
if (name.equals(CloudEventV1.DATACONTENTTYPE)) {
|
||||
message.setContentType(value);
|
||||
} else {
|
||||
// for now, extensions are mapped to application-properties
|
||||
// see https://github.com/cloudevents/sdk-java/issues/30#issuecomment-723982190
|
||||
if (applicationProperties == null) {
|
||||
throw new IllegalStateException("This Writer is not initialized");
|
||||
}
|
||||
String propName = AmqpConstants.ATTRIBUTES_TO_PROPERTYNAMES.get(name);
|
||||
if (propName == null) {
|
||||
propName = name;
|
||||
}
|
||||
applicationProperties.getValue().put(propName, value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProtonAmqpMessageWriter<R> create(final SpecVersion version) {
|
||||
if (applicationProperties == null) {
|
||||
applicationProperties = new ApplicationProperties(new HashMap<>());
|
||||
}
|
||||
applicationProperties.getValue().put(AmqpConstants.APP_PROPERTY_SPEC_VERSION, version.toString());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message setEvent(final EventFormat format, final byte[] value) throws CloudEventRWException {
|
||||
message.setContentType(format.serializedContentType());
|
||||
message.setBody(new Data(new Binary(value)));
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message end(final CloudEventData data) throws CloudEventRWException {
|
||||
message.setBody(new Data(new Binary(data.toBytes())));
|
||||
message.setApplicationProperties(applicationProperties);
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message end() {
|
||||
message.setBody(null);
|
||||
message.setApplicationProperties(applicationProperties);
|
||||
return message;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
/*
|
||||
* 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.amqp;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.amqp.impl.AmqpConstants;
|
||||
import io.cloudevents.core.message.Encoding;
|
||||
import io.cloudevents.core.message.MessageReader;
|
||||
import io.cloudevents.core.mock.CSVFormat;
|
||||
import io.cloudevents.core.test.Data;
|
||||
import io.cloudevents.core.v03.CloudEventV03;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.types.Time;
|
||||
import org.apache.qpid.proton.amqp.Binary;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Section;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* Tests verifying the behavior of the {@code ProtonAmqpMessageFactory}.
|
||||
*/
|
||||
public class ProtonAmqpMessageFactoryTest {
|
||||
|
||||
private static final String PREFIX_TEMPLATE = AmqpConstants.CE_PREFIX + "%s";
|
||||
private static final String DATACONTENTTYPE_NULL = null;
|
||||
private static final byte[] DATAPAYLOAD_NULL = null;
|
||||
|
||||
@ParameterizedTest()
|
||||
@MethodSource("binaryTestArguments")
|
||||
public void readBinary(final Map<String, Object> props, final String contentType, final byte[] body,
|
||||
final CloudEvent event) {
|
||||
final Section bodySection = body != null ? new org.apache.qpid.proton.amqp.messaging.Data(new Binary(body)) : null;
|
||||
final MessageReader amqpReader = ProtonAmqpMessageFactory.createReader(contentType, new ApplicationProperties(props), bodySection);
|
||||
assertThat(amqpReader.getEncoding()).isEqualTo(Encoding.BINARY);
|
||||
assertThat(amqpReader.toEvent()).isEqualTo(event);
|
||||
}
|
||||
|
||||
@ParameterizedTest()
|
||||
@MethodSource("io.cloudevents.core.test.Data#allEventsWithoutExtensions")
|
||||
public void readStructured(final CloudEvent event) {
|
||||
final String contentType = CSVFormat.INSTANCE.serializedContentType() + "; charset=utf8";
|
||||
final byte[] contentPayload = CSVFormat.INSTANCE.serialize(event);
|
||||
|
||||
final MessageReader amqpReader = ProtonAmqpMessageFactory.createReader(contentType, null, new org.apache.qpid.proton.amqp.messaging.Data(new Binary(contentPayload)));
|
||||
assertThat(amqpReader.getEncoding()).isEqualTo(Encoding.STRUCTURED);
|
||||
assertThat(amqpReader.toEvent()).isEqualTo(event);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> binaryTestArguments() {
|
||||
|
||||
return Stream.of(
|
||||
// V03
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()),
|
||||
property(CloudEventV03.ID, Data.ID),
|
||||
property(CloudEventV03.TYPE, Data.TYPE),
|
||||
property(CloudEventV03.SOURCE, Data.SOURCE),
|
||||
property("ignored", "ignore")
|
||||
),
|
||||
DATACONTENTTYPE_NULL,
|
||||
DATAPAYLOAD_NULL,
|
||||
Data.V03_MIN
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()),
|
||||
property(CloudEventV03.ID, Data.ID),
|
||||
property(CloudEventV03.TYPE, Data.TYPE),
|
||||
property(CloudEventV03.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV03.SCHEMAURL, Data.DATASCHEMA.toString()),
|
||||
property(CloudEventV03.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV03.TIME, Time.writeTime(Data.TIME)),
|
||||
property("ignored", "ignore")
|
||||
),
|
||||
Data.DATACONTENTTYPE_JSON,
|
||||
Data.DATA_JSON_SERIALIZED,
|
||||
Data.V03_WITH_JSON_DATA
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()),
|
||||
property(CloudEventV03.ID, Data.ID),
|
||||
property(CloudEventV03.TYPE, Data.TYPE),
|
||||
property(CloudEventV03.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV03.SCHEMAURL, Data.DATASCHEMA.toString()),
|
||||
property(CloudEventV03.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV03.TIME, Time.writeTime(Data.TIME)),
|
||||
property("astring", "aaa"),
|
||||
property("aboolean", "true"),
|
||||
property("anumber", "10"),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_JSON,
|
||||
Data.DATA_JSON_SERIALIZED,
|
||||
Data.V03_WITH_JSON_DATA_WITH_EXT_STRING
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()),
|
||||
property(CloudEventV03.ID, Data.ID),
|
||||
property(CloudEventV03.TYPE, Data.TYPE),
|
||||
property(CloudEventV03.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV03.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV03.TIME, Time.writeTime(Data.TIME)),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_XML,
|
||||
Data.DATA_XML_SERIALIZED,
|
||||
Data.V03_WITH_XML_DATA
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV03.SPECVERSION, SpecVersion.V03.toString()),
|
||||
property(CloudEventV03.ID, Data.ID),
|
||||
property(CloudEventV03.TYPE, Data.TYPE),
|
||||
property(CloudEventV03.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV03.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV03.TIME, Time.writeTime(Data.TIME)),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_TEXT,
|
||||
Data.DATA_TEXT_SERIALIZED,
|
||||
Data.V03_WITH_TEXT_DATA
|
||||
),
|
||||
// V1
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()),
|
||||
property(CloudEventV1.ID, Data.ID),
|
||||
property(CloudEventV1.TYPE, Data.TYPE),
|
||||
property(CloudEventV1.SOURCE, Data.SOURCE.toString()),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
DATACONTENTTYPE_NULL,
|
||||
DATAPAYLOAD_NULL,
|
||||
Data.V1_MIN
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()),
|
||||
property(CloudEventV1.ID, Data.ID),
|
||||
property(CloudEventV1.TYPE, Data.TYPE),
|
||||
property(CloudEventV1.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV1.DATASCHEMA, Data.DATASCHEMA.toString()),
|
||||
property(CloudEventV1.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV1.TIME, Time.writeTime(Data.TIME)),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_JSON,
|
||||
Data.DATA_JSON_SERIALIZED,
|
||||
Data.V1_WITH_JSON_DATA
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()),
|
||||
property(CloudEventV1.ID, Data.ID),
|
||||
property(CloudEventV1.TYPE, Data.TYPE),
|
||||
property(CloudEventV1.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV1.DATASCHEMA, Data.DATASCHEMA.toString()),
|
||||
property(CloudEventV1.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV1.TIME, Time.writeTime(Data.TIME)),
|
||||
property("astring", "aaa"),
|
||||
property("aboolean", "true"),
|
||||
property("anumber", "10"),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_JSON,
|
||||
Data.DATA_JSON_SERIALIZED,
|
||||
Data.V1_WITH_JSON_DATA_WITH_EXT_STRING
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()),
|
||||
property(CloudEventV1.ID, Data.ID),
|
||||
property(CloudEventV1.TYPE, Data.TYPE),
|
||||
property(CloudEventV1.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV1.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV1.TIME, Time.writeTime(Data.TIME)),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_XML,
|
||||
Data.DATA_XML_SERIALIZED,
|
||||
Data.V1_WITH_XML_DATA
|
||||
),
|
||||
Arguments.of(
|
||||
properties(
|
||||
property(CloudEventV1.SPECVERSION, SpecVersion.V1.toString()),
|
||||
property(CloudEventV1.ID, Data.ID),
|
||||
property(CloudEventV1.TYPE, Data.TYPE),
|
||||
property(CloudEventV1.SOURCE, Data.SOURCE.toString()),
|
||||
property(CloudEventV1.SUBJECT, Data.SUBJECT),
|
||||
property(CloudEventV1.TIME, Time.writeTime(Data.TIME)),
|
||||
property("ignored", "ignored")
|
||||
),
|
||||
Data.DATACONTENTTYPE_TEXT,
|
||||
Data.DATA_TEXT_SERIALIZED,
|
||||
Data.V1_WITH_TEXT_DATA
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private static final SimpleEntry<String, Object> property(final String name, final Object value) {
|
||||
return name.equalsIgnoreCase("ignored") ?
|
||||
new SimpleEntry<>(name, value) :
|
||||
new SimpleEntry<>(String.format(PREFIX_TEMPLATE, name), value);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static final Map<String, Object> properties(final SimpleEntry<String, Object>... entries) {
|
||||
return Stream.of(entries)
|
||||
.collect(Collectors.toMap(SimpleEntry::getKey, SimpleEntry::getValue));
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* 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.amqp;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.apache.qpid.proton.amqp.Binary;
|
||||
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
|
||||
import org.apache.qpid.proton.amqp.messaging.Section;
|
||||
import org.apache.qpid.proton.message.Message;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.amqp.impl.ProtonAmqpMessageWriter;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.core.message.MessageWriter;
|
||||
import io.cloudevents.core.mock.CSVFormat;
|
||||
|
||||
/**
|
||||
* Tests verifying the behavior of the {@code ProtonAmqpMessageWriter}.
|
||||
*/
|
||||
public class ProtonAmqpMessageWriterTest {
|
||||
|
||||
/**
|
||||
* Verifies that a binary CloudEvent message can be successfully represented
|
||||
* as an AMQP message.
|
||||
*/
|
||||
@ParameterizedTest()
|
||||
@MethodSource("io.cloudevents.core.test.Data#allEventsWithStringExtensions")
|
||||
public void testWriteBinaryCloudEventToAmqpRepresentation(final CloudEvent binaryEvent) {
|
||||
|
||||
final Message expectedMessage = translateBinaryEvent(binaryEvent);
|
||||
|
||||
final MessageWriter<?, Message> writer = new ProtonAmqpMessageWriter<Message>();
|
||||
final Message actualMessage = writer.writeBinary(binaryEvent);
|
||||
|
||||
assertThat(actualMessage.getContentType()).isEqualTo(expectedMessage.getContentType());
|
||||
assertThat(Objects.toString(actualMessage.getBody())).isEqualTo(Objects.toString(expectedMessage.getBody()));
|
||||
assertThat(actualMessage.getApplicationProperties().getValue()).
|
||||
isEqualTo(expectedMessage.getApplicationProperties().getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that a structured CloudEvent message (in CSV format) can be successfully represented
|
||||
* as an AMQP message.
|
||||
*/
|
||||
@ParameterizedTest()
|
||||
@MethodSource("io.cloudevents.core.test.Data#allEventsWithoutExtensions")
|
||||
public void testWriteStructuredCloudEventToAmqpRepresentation(final CloudEvent event) {
|
||||
final EventFormat format = CSVFormat.INSTANCE;
|
||||
final Message expectedMessage = translateStructured(event, format);
|
||||
|
||||
final MessageWriter<?, Message> writer = new ProtonAmqpMessageWriter<Message>();
|
||||
final Message actualMessage = writer.writeStructured(event, format.serializedContentType());
|
||||
|
||||
assertThat(actualMessage.getContentType()).isEqualTo(expectedMessage.getContentType());
|
||||
assertThat(Objects.toString(actualMessage.getBody())).isEqualTo(Objects.toString(expectedMessage.getBody()));
|
||||
assertThat(actualMessage.getApplicationProperties()).isNull();
|
||||
}
|
||||
|
||||
private Message translateBinaryEvent(final CloudEvent event) {
|
||||
|
||||
final Message message = Message.Factory.create();
|
||||
|
||||
final Map<String, Object> map = new HashMap<>();
|
||||
|
||||
if (!event.getAttributeNames().isEmpty()) {
|
||||
event.getAttributeNames().forEach(name -> {
|
||||
if (name.equals("datacontentencoding")) {
|
||||
// ignore
|
||||
} else if (name.equals("datacontenttype") && event.getAttribute(name) != null) {
|
||||
message.setContentType(event.getAttribute(name).toString());
|
||||
} else {
|
||||
addProperty(map, name, Objects.toString(event.getAttribute(name)), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (!event.getExtensionNames().isEmpty()) {
|
||||
event.getExtensionNames().forEach(name -> addProperty(map, name, Objects.toString(event.getExtension(name)), false));
|
||||
}
|
||||
|
||||
if (event.getData() != null) {
|
||||
final Section payload = new org.apache.qpid.proton.amqp.messaging.Data(new Binary(event.getData().toBytes()));
|
||||
message.setBody(payload);
|
||||
}
|
||||
|
||||
message.setApplicationProperties(new ApplicationProperties(map));
|
||||
return message;
|
||||
}
|
||||
|
||||
private Message translateStructured(final CloudEvent event, final EventFormat format) {
|
||||
final Message message = Message.Factory.create();
|
||||
message.setContentType(format.serializedContentType());
|
||||
message.setBody(new org.apache.qpid.proton.amqp.messaging.Data(new Binary(format.serialize(event))));
|
||||
return message;
|
||||
}
|
||||
private void addProperty(final Map<String, Object> map, final String name, final String value, final boolean prefix) {
|
||||
if (prefix) {
|
||||
map.put(String.format("cloudEvents:%s", name), value);
|
||||
} else {
|
||||
map.put(name, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,26 +1,5 @@
|
|||
# CloudEvents API
|
||||
|
||||
For Maven based projects, use the following dependency:
|
||||
Javadoc: [](http://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
|
||||
|
||||
```xml
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>2.0.0-milestone1</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
This package provides the base interfaces used by the SDK. In particular:
|
||||
|
||||
- `CloudEvent` is the main interface representing a read only CloudEvent in-memory representation
|
||||
- `Extension` represents a _materialized_ in-memory representation of a CloudEvent extension
|
||||
- `SpecVersion` is an enum of CloudEvents' specification versions supported by this SDK version.
|
||||
- `CloudEventVisitor`/`CloudEventVisitable` are the interfaces used by the SDK to implement protocol bindings/event formats.
|
||||
A 3rd party implementer can implement these interfaces directly in its `CloudEvent` in order
|
||||
to customize/implement efficiently the marshalling/unmarshalling process.
|
||||
These interfaces are optional and, if your `CloudEvent` doesn't implement it, a default implementation is provided by the SDK.
|
||||
|
||||
`cloudevents-core` provides the implementation of these interfaces, and a 3rd party implementer can grab this package
|
||||
to implement specialized CloudEvent in-memory representations.
|
||||
Documentation: https://cloudevents.github.io/sdk-java/api
|
||||
|
|
20
api/pom.xml
20
api/pom.xml
|
@ -24,12 +24,11 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0-milestone1</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<name>CloudEvents - API</name>
|
||||
<version>${project.parent.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
|
@ -57,21 +56,4 @@
|
|||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Automatic-Module-Name>${module-name}</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -20,13 +20,13 @@ import io.cloudevents.lang.Nullable;
|
|||
|
||||
/**
|
||||
* Interface representing an in memory read only representation of a CloudEvent,
|
||||
* as specified by the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md">CloudEvents specification</a>
|
||||
* as specified by the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md">CloudEvents specification</a>.
|
||||
*/
|
||||
public interface CloudEvent extends CloudEventAttributes, CloudEventExtensions {
|
||||
public interface CloudEvent extends CloudEventContext {
|
||||
|
||||
/**
|
||||
* The event data
|
||||
* @return The event data, if any, otherwise {@code null}
|
||||
*/
|
||||
@Nullable
|
||||
byte[] getData();
|
||||
CloudEventData getData();
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ import io.cloudevents.lang.Nullable;
|
|||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -77,7 +77,7 @@ public interface CloudEventAttributes {
|
|||
* @return <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#time">Timestamp</a> of when the occurrence happened
|
||||
*/
|
||||
@Nullable
|
||||
ZonedDateTime getTime();
|
||||
OffsetDateTime getTime();
|
||||
|
||||
/**
|
||||
* Get the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#context-attributes">context attribute</a> named {@code attributeName}
|
||||
|
|
|
@ -14,15 +14,10 @@
|
|||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package io.cloudevents;
|
||||
|
||||
package io.cloudevents.http.restful.ws.impl;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static boolean isCloudEventEntity(Object obj) {
|
||||
return obj != null && CloudEvent.class.isAssignableFrom(obj.getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface representing an in memory read only representation of CloudEvent attributes and extensions.
|
||||
*/
|
||||
public interface CloudEventContext extends CloudEventAttributes, CloudEventExtensions {
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Interface that defines a wrapper for CloudEvent data.
|
||||
* <p>
|
||||
* This interface can be overridden to include any type of data inside a {@link CloudEvent}, given it has a method to convert back to bytes.
|
||||
*/
|
||||
public interface CloudEventData {
|
||||
|
||||
/**
|
||||
* Returns the bytes representation of this data instance.
|
||||
* <p>
|
||||
* Note: depending on the implementation, this operation may be expensive.
|
||||
*
|
||||
* @return this data, represented as bytes.
|
||||
*/
|
||||
byte[] toBytes();
|
||||
|
||||
}
|
|
@ -23,23 +23,23 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Materialized CloudEvent Extension interface to read/write the extension attributes key/values.
|
||||
* Materialized CloudEvent extension interface to read/write the extension attributes key/values.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface Extension {
|
||||
public interface CloudEventExtension {
|
||||
|
||||
/**
|
||||
* Fill this materialized extension with values from a {@link CloudEventExtensions} implementation
|
||||
* Fill this materialized extension with values from a {@link CloudEventExtensions} implementation.
|
||||
*
|
||||
* @param extensions
|
||||
* @param extensions the extensions where to read from
|
||||
*/
|
||||
void readFrom(CloudEventExtensions extensions);
|
||||
|
||||
/**
|
||||
* Get the attribute of extension named {@code key}
|
||||
* Get the attribute of extension named {@code key}.
|
||||
*
|
||||
* @param key the name of the extension attribute
|
||||
* @return the extension value
|
||||
* @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
|
|
@ -23,7 +23,7 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* The event extensions
|
||||
* The event extensions.
|
||||
* <p>
|
||||
* Extensions values could be String/Number/Boolean
|
||||
*/
|
||||
|
@ -34,7 +34,7 @@ public interface CloudEventExtensions {
|
|||
* Get the extension attribute named {@code extensionName}
|
||||
*
|
||||
* @param extensionName the extension name
|
||||
* @return the extension value or null if this instance doesn't contain such extension
|
||||
* @return the extension value in one of the valid types String/Number/Boolean or null if this instance doesn't contain such extension
|
||||
*/
|
||||
@Nullable
|
||||
Object getExtension(String extensionName);
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
|
||||
package io.cloudevents;
|
||||
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -27,11 +29,17 @@ import java.util.stream.Stream;
|
|||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public enum SpecVersion {
|
||||
/**
|
||||
* @see <a href="https://github.com/cloudevents/spec/releases/tag/v0.3">CloudEvents release v0.3</a>
|
||||
*/
|
||||
V03(
|
||||
"0.3",
|
||||
Arrays.asList("specversion", "id", "type", "source"),
|
||||
Arrays.asList("datacontenttype", "datacontentencoding", "schemaurl", "subject", "time")
|
||||
),
|
||||
/**
|
||||
* @see <a href="https://github.com/cloudevents/spec/releases/tag/v1.0">CloudEvents release v1.0</a>
|
||||
*/
|
||||
V1(
|
||||
"1.0",
|
||||
Arrays.asList("specversion", "id", "type", "source"),
|
||||
|
@ -62,7 +70,7 @@ public enum SpecVersion {
|
|||
*
|
||||
* @param sv String representing the spec version
|
||||
* @return The parsed spec version
|
||||
* @throws IllegalArgumentException When the spec version string is unrecognized
|
||||
* @throws CloudEventRWException When the spec version string is unrecognized
|
||||
*/
|
||||
public static SpecVersion parse(String sv) {
|
||||
switch (sv) {
|
||||
|
@ -71,7 +79,7 @@ public enum SpecVersion {
|
|||
case "1.0":
|
||||
return SpecVersion.V1;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unrecognized SpecVersion " + sv);
|
||||
throw CloudEventRWException.newInvalidSpecVersion(sv);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,9 @@ import static java.lang.annotation.ElementType.*;
|
|||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
import static javax.annotation.meta.When.MAYBE;
|
||||
|
||||
/**
|
||||
* This annotation is used to define a method parameter or return type as nullable.
|
||||
*/
|
||||
@Target(value = {METHOD, PARAMETER, FIELD})
|
||||
@Retention(value = RUNTIME)
|
||||
@Documented
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* Interface to write the attributes from a {@link io.cloudevents.rw.CloudEventReader} to a new representation.
|
||||
*/
|
||||
public interface CloudEventAttributesWriter {
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link String}. This setter should not be invoked for specversion, because the built Visitor already
|
||||
* has the information through the {@link CloudEventWriterFactory}.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @throws CloudEventRWException
|
||||
*/
|
||||
void setAttribute(String name, String value) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @throws CloudEventRWException
|
||||
*/
|
||||
default void setAttribute(String name, URI value) throws CloudEventRWException {
|
||||
setAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link ZonedDateTime} attribute.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @throws CloudEventRWException
|
||||
*/
|
||||
default void setAttribute(String name, ZonedDateTime value) throws CloudEventRWException {
|
||||
setAttribute(name, value.format(Time.RFC3339_DATE_FORMAT));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* Represents an object that can be read as CloudEvent context attributes and extensions.
|
||||
* <p>
|
||||
* An object (in particular, buffered objects) can implement both this interface and {@link CloudEventReader}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface CloudEventContextReader {
|
||||
|
||||
/**
|
||||
* Read the context attributes and extensions using the provided writer
|
||||
*
|
||||
* @param writer context writer
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
*/
|
||||
void readContext(CloudEventContextWriter writer) throws CloudEventRWException;
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Interface to write the context attributes/extensions from a {@link io.cloudevents.rw.CloudEventContextReader} to a new representation.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface CloudEventContextWriter {
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link String}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link OffsetDateTime} attribute.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this attribute.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, Time.writeTime(name, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Number}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*
|
||||
* @deprecated CloudEvent specification only permits {@link Integer} type as a
|
||||
* numeric value.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Integer}.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link Boolean} attribute.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with a binary type.
|
||||
* This setter should not be invoked for specversion, because the writer should
|
||||
* already know the specversion or because it doesn't need it to correctly write the value.
|
||||
*
|
||||
* @param name name of the attribute
|
||||
* @param value value of the attribute
|
||||
* @return self
|
||||
* @throws CloudEventRWException if anything goes wrong while writing this extension.
|
||||
* @throws IllegalArgumentException if you're trying to set the specversion attribute.
|
||||
*/
|
||||
default CloudEventContextWriter withContextAttribute(String name, byte[] value) throws CloudEventRWException {
|
||||
return withContextAttribute(name, Base64.getEncoder().encodeToString(value));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* Interface to convert a {@link CloudEventData} instance to another one.
|
||||
*
|
||||
* @param <R> the returned {@link CloudEventData} from this mapper.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface CloudEventDataMapper<R extends CloudEventData> {
|
||||
|
||||
/**
|
||||
* Map {@code data} to another {@link CloudEventData} instance.
|
||||
*
|
||||
* @param data the input data
|
||||
* @return The new data
|
||||
* @throws CloudEventRWException is anything goes wrong while mapping the input data
|
||||
*/
|
||||
R map(CloudEventData data) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* @return No-op identity mapper which can be used as default when no mapper is provided.
|
||||
*/
|
||||
static CloudEventDataMapper<CloudEventData> identity() {
|
||||
return d -> d;
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
/**
|
||||
* Interface to write the extensions from a {@link io.cloudevents.rw.CloudEventReader} to a new representation.
|
||||
*/
|
||||
public interface CloudEventExtensionsWriter {
|
||||
|
||||
/**
|
||||
* Set an extension with type {@link String}.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @throws CloudEventRWException
|
||||
*/
|
||||
void setExtension(String name, String value) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link URI}.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @throws CloudEventRWException
|
||||
*/
|
||||
default void setExtension(String name, Number value) throws CloudEventRWException {
|
||||
setExtension(name, value.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attribute with type {@link ZonedDateTime} attribute.
|
||||
*
|
||||
* @param name
|
||||
* @param value
|
||||
* @throws CloudEventRWException
|
||||
*/
|
||||
default void setExtension(String name, Boolean value) throws CloudEventRWException {
|
||||
setExtension(name, value.toString());
|
||||
}
|
||||
|
||||
}
|
|
@ -17,45 +17,96 @@
|
|||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
||||
/**
|
||||
* This class is the exception Protocol Binding and Event Format implementers can use to signal errors while serializing/deserializing CloudEvent.
|
||||
*/
|
||||
public class CloudEventRWException extends RuntimeException {
|
||||
|
||||
/**
|
||||
* The kind of error that happened while serializing/deserializing
|
||||
*/
|
||||
public enum CloudEventRWExceptionKind {
|
||||
/**
|
||||
* Spec version string is not recognized by this particular SDK version.
|
||||
*/
|
||||
INVALID_SPEC_VERSION,
|
||||
/**
|
||||
* The attribute name is not a valid/known context attribute.
|
||||
*/
|
||||
INVALID_ATTRIBUTE_NAME,
|
||||
/**
|
||||
* The extension name is not valid because it doesn't follow the naming convention enforced by the CloudEvents spec.
|
||||
*
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#attribute-naming-convention">naming convention</a>
|
||||
*/
|
||||
INVALID_EXTENSION_NAME,
|
||||
/**
|
||||
* The attribute/extension type is not valid.
|
||||
*/
|
||||
INVALID_ATTRIBUTE_TYPE,
|
||||
/**
|
||||
* The attribute/extension value is not valid.
|
||||
*/
|
||||
INVALID_ATTRIBUTE_VALUE,
|
||||
INVALID_EXTENSION_TYPE,
|
||||
/**
|
||||
* The data type is not valid.
|
||||
*/
|
||||
INVALID_DATA_TYPE,
|
||||
/**
|
||||
* Error while converting CloudEventData.
|
||||
*/
|
||||
DATA_CONVERSION,
|
||||
/**
|
||||
* Invalid content type or spec version
|
||||
*/
|
||||
UNKNOWN_ENCODING,
|
||||
/**
|
||||
* Other error.
|
||||
*/
|
||||
OTHER
|
||||
}
|
||||
|
||||
private final CloudEventRWExceptionKind kind;
|
||||
|
||||
public CloudEventRWException(CloudEventRWExceptionKind kind, Throwable cause) {
|
||||
private CloudEventRWException(CloudEventRWExceptionKind kind, Throwable cause) {
|
||||
super(cause);
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public CloudEventRWException(CloudEventRWExceptionKind kind, String message) {
|
||||
private CloudEventRWException(CloudEventRWExceptionKind kind, String message) {
|
||||
super(message);
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
public CloudEventRWException(CloudEventRWExceptionKind kind, String message, Throwable cause) {
|
||||
private CloudEventRWException(CloudEventRWExceptionKind kind, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.kind = kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@link CloudEventRWExceptionKind} associated to this exception instance.
|
||||
*/
|
||||
public CloudEventRWExceptionKind getKind() {
|
||||
return kind;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param specVersion the invalid input spec version
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidSpecVersion(String specVersion) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
CloudEventRWExceptionKind.INVALID_SPEC_VERSION,
|
||||
"Invalid specversion: " + specVersion
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidAttributeName(String attributeName) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_NAME,
|
||||
|
@ -63,32 +114,117 @@ public class CloudEventRWException extends RuntimeException {
|
|||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newInvalidAttributeType(String attributeName, Class<?> clazz) {
|
||||
/**
|
||||
* @param extensionName the invalid extension name
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidExtensionName(String extensionName) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
"Invalid attribute type for \"" + attributeName + "\": " + clazz.getCanonicalName()
|
||||
CloudEventRWExceptionKind.INVALID_EXTENSION_NAME,
|
||||
"Invalid extensions name: " + extensionName
|
||||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newInvalidAttributeValue(String attributeName, Object value, Throwable cause) {
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @param clazz the type of the attribute
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidAttributeType(String attributeName, Class<?> clazz) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
"Invalid attribute/extension type for \"" + attributeName + "\": " + clazz.getCanonicalName()
|
||||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newInvalidAttributeType(String attributeName,Object value){
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_TYPE,
|
||||
"Invalid attribute/extension type for \""
|
||||
+ attributeName
|
||||
+ "\": Type" + value.getClass().getCanonicalName()
|
||||
+ ". Value: " + value
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param attributeName the invalid attribute name
|
||||
* @param value the value of the attribute
|
||||
* @param cause an optional cause identifying the eventual validation error
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidAttributeValue(String attributeName, Object value, @Nullable Throwable cause) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_ATTRIBUTE_VALUE,
|
||||
"Invalid attribute value for \"" + attributeName + "\": " + value,
|
||||
"Invalid attribute/extension value for \"" + attributeName + "\": " + value,
|
||||
cause
|
||||
);
|
||||
}
|
||||
|
||||
public static CloudEventRWException newInvalidExtensionType(String extensionName, Class<?> clazz) {
|
||||
/**
|
||||
* @param actual the actual data type
|
||||
* @param allowed the list of allowed data types
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newInvalidDataType(String actual, String... allowed) {
|
||||
String message;
|
||||
if (allowed.length == 0) {
|
||||
message = "Invalid data type: " + actual;
|
||||
} else {
|
||||
message = "Invalid data type: " + actual + ". Allowed: " + String.join(", ", allowed);
|
||||
}
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.INVALID_EXTENSION_TYPE,
|
||||
"Invalid extension type for \"" + extensionName + "\": " + clazz.getCanonicalName()
|
||||
CloudEventRWExceptionKind.INVALID_DATA_TYPE,
|
||||
message
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param cause the cause of the conversion failure
|
||||
* @param from the input data type
|
||||
* @param to the target data type
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newDataConversion(Throwable cause, String from, String to) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.DATA_CONVERSION,
|
||||
"Error while trying to convert data from " + from + " to " + to,
|
||||
cause
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a new {@link CloudEventRWException} instance.
|
||||
*/
|
||||
public static CloudEventRWException newUnknownEncodingException() {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.UNKNOWN_ENCODING,
|
||||
"Could not parse. Unknown encoding. Invalid content type or spec version"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This wraps a {@link Throwable} in a new generic instance of this exception.
|
||||
*
|
||||
* @param cause the cause of the exception
|
||||
* @return a new {@link CloudEventRWException} instance
|
||||
*/
|
||||
public static CloudEventRWException newOther(Throwable cause) {
|
||||
return new CloudEventRWException(
|
||||
CloudEventRWExceptionKind.OTHER,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,33 +17,42 @@
|
|||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* Represents an object that can be read as CloudEvent
|
||||
* Represents an object that can be read as CloudEvent.
|
||||
* <p>
|
||||
* The read may consume this object, hence it's not safe to invoke it multiple times, unless it's explicitly allowed by the implementer.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface CloudEventReader {
|
||||
|
||||
/**
|
||||
* Visit self using the provided visitor factory
|
||||
* Like {@link #read(CloudEventWriterFactory, CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @param <W> The type of the {@link CloudEventWriter} created by writerFactory
|
||||
* @param <R> The return value of the {@link CloudEventWriter} created by writerFactory
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @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.
|
||||
*/
|
||||
<V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory) throws CloudEventRWException;
|
||||
default <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException {
|
||||
return read(writerFactory, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Visit self attributes using the provided writer
|
||||
* Read self using the provided writer factory.
|
||||
*
|
||||
* @param writer Attributes writer
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @param <W> the {@link CloudEventWriter} type
|
||||
* @param <R> the return type of the {@link CloudEventWriter}
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @param mapper the mapper to invoke when building the {@link CloudEventData}
|
||||
* @return the value returned by {@link CloudEventWriter#end()} or {@link CloudEventWriter#end(CloudEventData)}
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
*/
|
||||
void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Visit self extensions using the provided writer
|
||||
*
|
||||
* @param visitor Extensions writer
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
*/
|
||||
void readExtensions(CloudEventExtensionsWriter visitor) throws CloudEventRWException;
|
||||
<W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException;
|
||||
|
||||
}
|
||||
|
|
|
@ -17,26 +17,31 @@
|
|||
|
||||
package io.cloudevents.rw;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
|
||||
/**
|
||||
* Interface to write the content (CloudEvents attributes, extensions and payload) from a
|
||||
* {@link io.cloudevents.rw.CloudEventReader} to a new representation.
|
||||
*
|
||||
* @param <R> return value at the end of the write process
|
||||
*/
|
||||
public interface CloudEventWriter<R> extends CloudEventAttributesWriter, CloudEventExtensionsWriter {
|
||||
public interface CloudEventWriter<R> extends CloudEventContextWriter {
|
||||
|
||||
/**
|
||||
* End the visit with a data field
|
||||
* End the write with a data payload.
|
||||
*
|
||||
* @param data the data to write
|
||||
* @return an eventual return value
|
||||
* @throws CloudEventRWException if the message writer cannot be ended.
|
||||
*/
|
||||
R end(byte[] value) throws CloudEventRWException;
|
||||
R end(CloudEventData data) throws CloudEventRWException;
|
||||
|
||||
/**
|
||||
* End the visit
|
||||
* End the write.
|
||||
*
|
||||
* @return an eventual return value
|
||||
* @throws CloudEventRWException if the message writer cannot be ended.
|
||||
*/
|
||||
R end();
|
||||
R end() throws CloudEventRWException;
|
||||
|
||||
}
|
||||
|
|
|
@ -19,14 +19,21 @@ package io.cloudevents.rw;
|
|||
|
||||
import io.cloudevents.SpecVersion;
|
||||
|
||||
/**
|
||||
* This factory is used to enforce setting the {@link SpecVersion} as first step in the writing process.
|
||||
*
|
||||
* @param <W> The type of the {@link CloudEventWriter} created by this factory
|
||||
* @param <R> The return value of the {@link CloudEventWriter} created by this factory
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CloudEventWriterFactory<V extends CloudEventWriter<R>, R> {
|
||||
public interface CloudEventWriterFactory<W extends CloudEventWriter<R>, R> {
|
||||
|
||||
/**
|
||||
* Create a {@link CloudEventWriter} starting from the provided {@link SpecVersion}
|
||||
*
|
||||
* @param version
|
||||
* @return
|
||||
* @param version the spec version to create the writer
|
||||
* @return the new writer
|
||||
* @throws CloudEventRWException if the spec version is invalid or the writer cannot be instantiated.
|
||||
*/
|
||||
V create(SpecVersion version);
|
||||
W create(SpecVersion version) throws CloudEventRWException;
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
|
||||
package io.cloudevents.types;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeFormatterBuilder;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.time.DateTimeException;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.temporal.ChronoField;
|
||||
|
||||
import static java.time.format.DateTimeFormatter.ISO_OFFSET_DATE_TIME;
|
||||
|
||||
/**
|
||||
* Utilities to handle the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#type-system">CloudEvent Attribute Timestamp type</a>
|
||||
|
@ -31,23 +33,57 @@ public final class Time {
|
|||
private Time() {
|
||||
}
|
||||
|
||||
public static final DateTimeFormatter RFC3339_DATE_FORMAT = new DateTimeFormatterBuilder()
|
||||
.appendPattern("yyyy-MM-dd'T'HH:mm:ss")
|
||||
.appendFraction(ChronoField.NANO_OF_SECOND, 0, 9, true)
|
||||
.appendZoneOrOffsetId()
|
||||
.toFormatter();
|
||||
|
||||
/**
|
||||
* Parse a {@link String} RFC3339 compliant as {@link ZonedDateTime}
|
||||
* Parse a {@link String} RFC3339 compliant as {@link OffsetDateTime}.
|
||||
*
|
||||
* @param time the value to parse as time
|
||||
* @return the parsed {@link OffsetDateTime}
|
||||
* @throws DateTimeParseException if something went wrong when parsing the provided time.
|
||||
*/
|
||||
public static ZonedDateTime parseTime(String time) throws DateTimeParseException {
|
||||
return ZonedDateTime.parse(time, RFC3339_DATE_FORMAT);
|
||||
public static OffsetDateTime parseTime(String time) throws DateTimeParseException {
|
||||
return OffsetDateTime.parse(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link ZonedDateTime} to {@link String}
|
||||
* Parse an attribute/extension with RFC3339 compliant {@link String} value as {@link OffsetDateTime}.
|
||||
*
|
||||
* @param attributeName the attribute/extension name
|
||||
* @param time the value to parse as time
|
||||
* @return the parsed {@link OffsetDateTime}
|
||||
* @throws CloudEventRWException if something went wrong when parsing the attribute/extension.
|
||||
*/
|
||||
public static String writeTime(ZonedDateTime time) throws DateTimeParseException {
|
||||
return time.format(RFC3339_DATE_FORMAT);
|
||||
public static OffsetDateTime parseTime(String attributeName, String time) throws CloudEventRWException {
|
||||
try {
|
||||
return parseTime(time);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue(attributeName, time, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link OffsetDateTime} to a RFC3339 compliant {@link String}.
|
||||
*
|
||||
* @param time the time to write as {@link String}
|
||||
* @return the serialized time
|
||||
* @throws DateTimeException if something went wrong when serializing the provided time.
|
||||
*/
|
||||
public static String writeTime(OffsetDateTime time) throws DateTimeException {
|
||||
return ISO_OFFSET_DATE_TIME.format(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an attribute/extension {@link OffsetDateTime} to a RFC3339 compliant {@link String}.
|
||||
*
|
||||
* @param attributeName the attribute/extension name
|
||||
* @param time the time to write as {@link String}
|
||||
* @return the serialized time
|
||||
* @throws CloudEventRWException if something went wrong when serializing the attribute/extension.
|
||||
*/
|
||||
public static String writeTime(String attributeName, OffsetDateTime time) throws DateTimeException {
|
||||
try {
|
||||
return writeTime(time);
|
||||
} catch (DateTimeParseException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue(attributeName, time, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package io.cloudevents;
|
||||
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
|
||||
class SpecVersionTest {
|
||||
|
||||
@Test
|
||||
void parse() {
|
||||
assertAll(
|
||||
() -> assertThat(SpecVersion.parse("1.0")).isEqualTo(SpecVersion.V1),
|
||||
() -> assertThat(SpecVersion.parse("0.3")).isEqualTo(SpecVersion.V03),
|
||||
() -> assertThatCode(() -> SpecVersion.parse("9000.1"))
|
||||
.hasMessage(CloudEventRWException.newInvalidSpecVersion("9000.1").getMessage())
|
||||
);
|
||||
}
|
||||
}
|
|
@ -17,23 +17,27 @@
|
|||
package io.cloudevents.types;
|
||||
|
||||
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
||||
|
||||
public class TimeTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("parseDateArguments")
|
||||
void testParseAndFormatDate(String ts) {
|
||||
ZonedDateTime zonedDateTime = Time.parseTime(ts);
|
||||
assertThat(ts).isEqualTo(zonedDateTime.format(Time.RFC3339_DATE_FORMAT));
|
||||
OffsetDateTime offsetDateTime = Time.parseTime(ts);
|
||||
assertThat(ts).isEqualTo(offsetDateTime.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -42,12 +46,27 @@ public class TimeTest {
|
|||
.isEqualTo("1937-01-01T12:00:27.87Z");
|
||||
}
|
||||
|
||||
@Test
|
||||
void testParseTimeException() {
|
||||
assertThatCode(() -> Time.parseTime("time", "01-01T12:20:27.87+00:20"))
|
||||
.isInstanceOf(CloudEventRWException.class)
|
||||
.hasMessage(CloudEventRWException.newInvalidAttributeValue("time", "01-01T12:20:27.87+00:20", null).getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSerializeDateOffset() {
|
||||
assertThat(Time.writeTime(OffsetDateTime.of(
|
||||
LocalDateTime.of(2020, 8, 3, 18, 10, 0, 0),
|
||||
ZoneOffset.ofHours(2)
|
||||
))).isEqualTo("2020-08-03T18:10:00+02:00");
|
||||
}
|
||||
|
||||
public static Stream<Arguments> parseDateArguments() {
|
||||
return Stream.of(
|
||||
Arguments.of("1985-04-12T23:20:50.52Z"),
|
||||
Arguments.of("1990-12-31T23:59:00Z"),
|
||||
Arguments.of("1990-12-31T15:59:00-08:00"),
|
||||
Arguments.of("1937-01-01T12:00:27.87+00:20")
|
||||
Arguments.of("1985-04-12T23:20:50.520Z"),
|
||||
Arguments.of("1990-12-31T23:59Z"),
|
||||
Arguments.of("1990-12-31T15:59-08:00"),
|
||||
Arguments.of("1937-01-01T12:00:27.870+00:20")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,174 @@
|
|||
<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">
|
||||
<!--
|
||||
~ 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.
|
||||
~
|
||||
-->
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-benchmarks</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>CloudEvents - Benchmarks</name>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<jmh.version>1.23</jmh.version>
|
||||
<javac.target>1.8</javac.target>
|
||||
<!-- Name of the benchmark Uber-JAR to generate. -->
|
||||
<uberjar.name>benchmarks</uberjar.name>
|
||||
<maven.javadoc.skip>true</maven.javadoc.skip>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-json-jackson</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-sql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-core</artifactId>
|
||||
<version>${jmh.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjdk.jmh</groupId>
|
||||
<artifactId>jmh-generator-annprocess</artifactId>
|
||||
<version>${jmh.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<compilerVersion>${javac.target}</compilerVersion>
|
||||
<source>${javac.target}</source>
|
||||
<target>${javac.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<finalName>${uberjar.name}</finalName>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<mainClass>org.openjdk.jmh.Main</mainClass>
|
||||
</transformer>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
</transformers>
|
||||
<filters>
|
||||
<filter>
|
||||
<!--
|
||||
Shading signed JARs will fail without this.
|
||||
http://stackoverflow.com/questions/999489/invalid-signature-file-when-attempting-to-run-a-jar
|
||||
-->
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>META-INF/*.SF</exclude>
|
||||
<exclude>META-INF/*.DSA</exclude>
|
||||
<exclude>META-INF/*.RSA</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<version>2.5</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-deploy-plugin</artifactId>
|
||||
<version>2.8.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-install-plugin</artifactId>
|
||||
<version>3.1.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.9.1</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-resources-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-site-plugin</artifactId>
|
||||
<version>3.3</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.17</version>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* 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.bench.jackson;
|
||||
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_JSON_DATA_WITH_EXT;
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_XML_DATA;
|
||||
|
||||
public class JsonFormatDeserializationBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class DeserializationState {
|
||||
|
||||
public byte[] eventWithJson;
|
||||
public byte[] eventWithXml;
|
||||
public JsonFormat format = new JsonFormat();
|
||||
|
||||
public DeserializationState() {
|
||||
eventWithJson = format.serialize(V1_WITH_JSON_DATA_WITH_EXT);
|
||||
eventWithXml = format.serialize(V1_WITH_XML_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void deserializeWithJsonData(DeserializationState state, Blackhole bh) {
|
||||
bh.consume(
|
||||
state.format.deserialize(state.eventWithJson)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void deserializeWithXmlData(DeserializationState state, Blackhole bh) {
|
||||
bh.consume(
|
||||
state.format.deserialize(state.eventWithXml)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.bench.jackson;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_JSON_DATA_WITH_EXT;
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_XML_DATA;
|
||||
|
||||
public class JsonFormatSerializationBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class SerializationState {
|
||||
public CloudEvent eventWithJson = CloudEventBuilder.v1(V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
public CloudEvent eventWithXml = CloudEventBuilder.v1(V1_WITH_XML_DATA).build();
|
||||
public JsonFormat format = new JsonFormat();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void serializeWithJsonData(SerializationState state, Blackhole bh) {
|
||||
bh.consume(
|
||||
state.format.serialize(state.eventWithJson)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void serializeWithXmlData(SerializationState state, Blackhole bh) {
|
||||
bh.consume(
|
||||
state.format.serialize(state.eventWithXml)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -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.bench.kafka;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
import io.cloudevents.kafka.KafkaMessageFactory;
|
||||
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 CloudEventToKafkaProducerMessageBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class Event {
|
||||
public CloudEvent event = CloudEventBuilder.v1(V1_WITH_JSON_DATA_WITH_EXT).build();
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void testBinaryEncoding(Event event, Blackhole bh) {
|
||||
bh.consume(
|
||||
KafkaMessageFactory
|
||||
.createWriter("aaa")
|
||||
.writeBinary(event.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void testStructuredJsonEncoding(Event event, Blackhole bh) {
|
||||
bh.consume(
|
||||
KafkaMessageFactory
|
||||
.createWriter("aaa")
|
||||
.writeStructured(event.event, JsonFormat.CONTENT_TYPE)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* 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.bench.kafka;
|
||||
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
import io.cloudevents.kafka.KafkaMessageFactory;
|
||||
import org.apache.kafka.clients.consumer.ConsumerRecord;
|
||||
import org.apache.kafka.clients.producer.ProducerRecord;
|
||||
import org.apache.kafka.common.record.TimestampType;
|
||||
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 KafkaConsumerMessageToCloudEventBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class BinaryMessage {
|
||||
public ConsumerRecord<String, byte[]> message;
|
||||
|
||||
public BinaryMessage() {
|
||||
// Hack to generate a consumer message
|
||||
ProducerRecord<Void, byte[]> inRecord = KafkaMessageFactory
|
||||
.createWriter("aaa")
|
||||
.writeBinary(V1_WITH_JSON_DATA_WITH_EXT);
|
||||
|
||||
this.message = new ConsumerRecord<>(
|
||||
"aaa",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
TimestampType.NO_TIMESTAMP_TYPE,
|
||||
-1L,
|
||||
ConsumerRecord.NULL_SIZE,
|
||||
ConsumerRecord.NULL_SIZE,
|
||||
"aaa",
|
||||
inRecord.value(),
|
||||
inRecord.headers()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void testBinaryEncoding(BinaryMessage binaryMessage, Blackhole bh) {
|
||||
bh.consume(
|
||||
KafkaMessageFactory
|
||||
.createReader(binaryMessage.message)
|
||||
.toEvent()
|
||||
);
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class StructuredJsonMessage {
|
||||
public ConsumerRecord<String, byte[]> message;
|
||||
|
||||
public StructuredJsonMessage() {
|
||||
// Hack to generate a consumer message
|
||||
ProducerRecord<Void, byte[]> inRecord = KafkaMessageFactory
|
||||
.createWriter("aaa")
|
||||
.writeStructured(V1_WITH_JSON_DATA_WITH_EXT, JsonFormat.CONTENT_TYPE);
|
||||
|
||||
this.message = new ConsumerRecord<>(
|
||||
"aaa",
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
TimestampType.NO_TIMESTAMP_TYPE,
|
||||
-1L,
|
||||
ConsumerRecord.NULL_SIZE,
|
||||
ConsumerRecord.NULL_SIZE,
|
||||
"aaa",
|
||||
inRecord.value(),
|
||||
inRecord.headers()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode(Mode.Throughput)
|
||||
public void testStructuredJsonEncoding(StructuredJsonMessage structuredJsonMessage, Blackhole bh) {
|
||||
bh.consume(
|
||||
KafkaMessageFactory
|
||||
.createReader(structuredJsonMessage.message)
|
||||
.toEvent()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package io.cloudevents.bench.sql;
|
||||
|
||||
import io.cloudevents.sql.Parser;
|
||||
import org.openjdk.jmh.annotations.Benchmark;
|
||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
||||
import org.openjdk.jmh.annotations.Mode;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
public class CompileBenchmark {
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(a + b + c) = 10 AND TRUE OR CONCAT('1', '2', id) = '123'")
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallTrueConstantExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(1 + 2 + 3) = 10 AND TRUE OR CONCAT('1', '2', '3') = 123")
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testCompileSmallFalseConstantExpression(Blackhole bh) {
|
||||
bh.consume(
|
||||
Parser.parseDefault("(1 + 2 + 3) = 10 AND FALSE OR CONCAT('1', '2', '3') = 124")
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.cloudevents.bench.sql;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.sql.Expression;
|
||||
import io.cloudevents.sql.Parser;
|
||||
import org.openjdk.jmh.annotations.*;
|
||||
import org.openjdk.jmh.infra.Blackhole;
|
||||
|
||||
import static io.cloudevents.core.test.Data.V1_WITH_JSON_DATA_WITH_EXT;
|
||||
|
||||
public class RunBenchmark {
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("a", "10")
|
||||
.withExtension("b", 3)
|
||||
.withExtension("c", "-3")
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(a + b + c) = 10 AND TRUE OR CONCAT('1', '2', id) = '123'");
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallTrueConstantExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(1 + 2 + 3) = 10 AND TRUE OR CONCAT('1', '2', '3') = 123");
|
||||
}
|
||||
|
||||
@State(Scope.Thread)
|
||||
public static class TestCaseSmallFalseConstantExpression {
|
||||
public CloudEvent event = CloudEventBuilder
|
||||
.v1(V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.build();
|
||||
public Expression expression = Parser
|
||||
.parseDefault("(1 + 2 + 3) = 10 AND FALSE OR CONCAT('1', '2', '3') = 124");
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallExpression(TestCaseSmallExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallExpression(TestCaseSmallExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallTrueConstantExpression(TestCaseSmallTrueConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallTrueConstantExpression(TestCaseSmallTrueConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testEvaluateSmallFalseConstantExpression(TestCaseSmallFalseConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.evaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
|
||||
public void testTryEvaluateSmallFalseConstantExpression(TestCaseSmallFalseConstantExpression testCase, Blackhole bh) {
|
||||
bh.consume(
|
||||
testCase.expression.tryEvaluate(testCase.event)
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2018-Present The CloudEvents Authors
|
||||
~ <p>
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~ <p>
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~ <p>
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
~
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-bom</artifactId>
|
||||
<name>CloudEvents - Bill of Materials</name>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-json-jackson</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-protobuf</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-amqp-proton</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-basic</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-vertx</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-http-restful-ws</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-kafka</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-spring</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-sql</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
|
@ -1,55 +1,5 @@
|
|||
# CloudEvents Core
|
||||
|
||||
The base classes, interfaces and low-level APIs to use CloudEvents.
|
||||
Javadoc: [](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
|
||||
|
||||
## How to Use
|
||||
|
||||
### Create an Event
|
||||
|
||||
```java
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import java.net.URI;
|
||||
|
||||
final CloudEvent event = CloudEventBuilder.v1()
|
||||
.withId("000")
|
||||
.withType("example.demo")
|
||||
.withSource(URI.create("http://example.com"))
|
||||
.withData("application/json", "{}".getBytes())
|
||||
.build();
|
||||
```
|
||||
|
||||
### Materialize an Extension
|
||||
|
||||
CloudEvent extensions can be materialized in their respective POJOs:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.core.extensions.ExtensionsParser;
|
||||
|
||||
DistributedTracingExtension dte = ExtensionsParser.getInstance()
|
||||
.parseExtension(DistributedTracingExtension.class, event);
|
||||
```
|
||||
|
||||
### Using Event Formats
|
||||
|
||||
The SDK implements [Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format) in submodules.
|
||||
To use them, you just need to add them as dependencies to your project and the SDK,
|
||||
through the `ServiceLoader` mechanism, will load them into the classpath.
|
||||
For example, to use the [JSON event format](https://github.com/cloudevents/spec/blob/v1.0/json-format.md) with Jackson,
|
||||
add `cloudevents-json-jackson` as a dependency and then:
|
||||
|
||||
```java
|
||||
import io.cloudevents.core.format.EventFormatProvider;
|
||||
import io.cloudevents.jackson.JsonFormat;
|
||||
|
||||
EventFormat format = EventFormatProvider
|
||||
.getInstance()
|
||||
.resolveFormat(JsonFormat.CONTENT_TYPE);
|
||||
|
||||
// Serialize event
|
||||
byte[] serialized = format.serialize(event);
|
||||
|
||||
// Deserialize event
|
||||
CloudEvent event = format.deserialize(bytes);
|
||||
```
|
||||
Documentation: https://cloudevents.github.io/sdk-java/core
|
||||
|
|
16
core/pom.xml
16
core/pom.xml
|
@ -22,12 +22,11 @@
|
|||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>2.0.0-milestone1</version>
|
||||
<version>4.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>cloudevents-core</artifactId>
|
||||
<name>CloudEvents - Core</name>
|
||||
<version>${project.parent.version}</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
|
@ -67,11 +66,6 @@
|
|||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
|
@ -83,15 +77,7 @@
|
|||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifestEntries>
|
||||
<Automatic-Module-Name>${module-name}</Automatic-Module-Name>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.impl.CloudEventContextReaderAdapter;
|
||||
import io.cloudevents.core.impl.CloudEventReaderAdapter;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.rw.CloudEventReader;
|
||||
|
||||
/**
|
||||
* This class contains a set of utility methods to deal with conversions of {@link io.cloudevents} related interfaces
|
||||
*/
|
||||
public final class CloudEventUtils {
|
||||
|
||||
private CloudEventUtils() {}
|
||||
|
||||
/**
|
||||
* Convert a {@link CloudEvent} to a {@link CloudEventReader}. This method provides a default implementation
|
||||
* for CloudEvent that doesn't implement {@link CloudEventReader}.
|
||||
* <p>
|
||||
* It's safe to use the returned {@link CloudEventReader} multiple times.
|
||||
*
|
||||
* @param event the event to convert
|
||||
* @return the reader implementation
|
||||
*/
|
||||
public static CloudEventReader toReader(CloudEvent event) {
|
||||
if (event instanceof CloudEventReader) {
|
||||
return (CloudEventReader) event;
|
||||
} else {
|
||||
return new CloudEventReaderAdapter(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link CloudEvent} to a {@link CloudEventContextReader}. This method provides a default implementation
|
||||
* for {@link CloudEvent} that doesn't implement {@link CloudEventContextReader}.
|
||||
* <p>
|
||||
* It's safe to use the returned {@link CloudEventReader} multiple times.
|
||||
*
|
||||
* @param event the event to convert
|
||||
* @return the context reader implementation
|
||||
*/
|
||||
public static CloudEventContextReader toContextReader(CloudEventContext event) {
|
||||
if (event instanceof CloudEventContextReader) {
|
||||
return (CloudEventContextReader) event;
|
||||
} else {
|
||||
return new CloudEventContextReaderAdapter(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link CloudEventReader} to a {@link CloudEvent}.
|
||||
*
|
||||
* @param reader the reader where to read the message from
|
||||
* @return the reader implementation
|
||||
*/
|
||||
public static CloudEvent toEvent(CloudEventReader reader) throws CloudEventRWException {
|
||||
return toEvent(reader, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a {@link CloudEventReader} to a {@link CloudEvent} mapping the data with the provided {@code mapper}.
|
||||
*
|
||||
* @param reader the reader where to read the message from
|
||||
* @param mapper the mapper to use when reading the data
|
||||
* @return the reader implementation
|
||||
*/
|
||||
public static CloudEvent toEvent(CloudEventReader reader, CloudEventDataMapper<?> mapper) throws CloudEventRWException {
|
||||
return reader.read(CloudEventBuilder::fromSpecVersion, mapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data contained in {@code event} and map it using the provided mapper.
|
||||
*
|
||||
* @param event the event eventually containing the data
|
||||
* @param mapper the mapper to use to map the data
|
||||
* @param <R> the returned {@link CloudEventData} implementation from the provided mapper
|
||||
* @return the data contained in {@code event} and mapped with {@code mapper}, if any, otherwise null
|
||||
*/
|
||||
@Nullable
|
||||
public static <R extends CloudEventData> R mapData(CloudEvent event, CloudEventDataMapper<R> mapper) {
|
||||
CloudEventData data = event.getData();
|
||||
if (data == null) {
|
||||
return null;
|
||||
}
|
||||
return mapper.map(data);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,15 +17,13 @@
|
|||
|
||||
package io.cloudevents.core.builder;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.*;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.ParametersAreNullableByDefault;
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
/**
|
||||
* Builder interface to build a {@link CloudEvent}.
|
||||
|
@ -87,7 +85,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param time time of the event
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withTime(ZonedDateTime time);
|
||||
CloudEventBuilder withTime(OffsetDateTime time);
|
||||
|
||||
/**
|
||||
* Set the {@code data} of the event
|
||||
|
@ -116,6 +114,55 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
*/
|
||||
CloudEventBuilder withData(String dataContentType, URI dataSchema, byte[] data);
|
||||
|
||||
/**
|
||||
* Set the {@code data} of the event
|
||||
*
|
||||
* @param data data of the event
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withData(CloudEventData data);
|
||||
|
||||
/**
|
||||
* Set the {@code datacontenttype} and {@code data} of the event
|
||||
*
|
||||
* @param dataContentType datacontenttype of the event
|
||||
* @param data data of the event
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withData(String dataContentType, CloudEventData data);
|
||||
|
||||
/**
|
||||
* Set the {@code datacontenttype}, {@code dataschema} and {@code data} of the event
|
||||
*
|
||||
* @param dataContentType datacontenttype of the event
|
||||
* @param dataSchema dataschema of the event
|
||||
* @param data data of the event
|
||||
* @return self
|
||||
*/
|
||||
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
|
||||
*
|
||||
|
@ -123,7 +170,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, String value);
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull String value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and numeric value
|
||||
|
@ -132,7 +179,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, Number value);
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull Number value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and boolean value
|
||||
|
@ -141,15 +188,58 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, boolean value);
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull Boolean value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and uri value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull URI value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and boolean value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull OffsetDateTime value);
|
||||
|
||||
/**
|
||||
* Set an extension with provided key and binary value
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @param value value of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull byte[] value);
|
||||
|
||||
/**
|
||||
* Add to the builder all the extension key/values of the provided extension
|
||||
*
|
||||
* @param extension materialized extension to set in the event
|
||||
* @param extension materialized extension to set in the builder
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withExtension(@Nonnull Extension extension);
|
||||
CloudEventBuilder withExtension(@Nonnull CloudEventExtension extension);
|
||||
|
||||
/**
|
||||
* Remove from the the builder the provided extension key, if any
|
||||
*
|
||||
* @param key key of the extension attribute
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutExtension(@Nonnull String key);
|
||||
|
||||
/**
|
||||
* Remove from the the builder the provided extension, if any
|
||||
*
|
||||
* @param extension materialized extension to remove from the builder
|
||||
* @return self
|
||||
*/
|
||||
CloudEventBuilder withoutExtension(@Nonnull CloudEventExtension extension);
|
||||
|
||||
/**
|
||||
* Build the event
|
||||
|
@ -214,4 +304,40 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder starting from the values of the provided event.
|
||||
*
|
||||
* @param event event to copy values from
|
||||
* @return the new builder
|
||||
*/
|
||||
static CloudEventBuilder from(@Nonnull CloudEvent event) {
|
||||
switch (event.getSpecVersion()) {
|
||||
case V1:
|
||||
return CloudEventBuilder.v1(event);
|
||||
case V03:
|
||||
return CloudEventBuilder.v03(event);
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"The provided spec version doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new builder starting from the values of the provided context.
|
||||
*
|
||||
* @param context context to copy values from
|
||||
* @return the new builder
|
||||
*/
|
||||
static CloudEventBuilder fromContext(@Nonnull CloudEventContext context) {
|
||||
switch (context.getSpecVersion()) {
|
||||
case V1:
|
||||
return new io.cloudevents.core.v1.CloudEventBuilder(context);
|
||||
case V03:
|
||||
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."
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
package io.cloudevents.core.data;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of {@link CloudEventData} that wraps a byte array.
|
||||
*/
|
||||
public class BytesCloudEventData implements CloudEventData {
|
||||
|
||||
private final byte[] value;
|
||||
|
||||
/**
|
||||
* @param value the bytes to wrap
|
||||
* @deprecated use {@link BytesCloudEventData#wrap(byte[])}
|
||||
*/
|
||||
public BytesCloudEventData(byte[] value) {
|
||||
Objects.requireNonNull(value);
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
BytesCloudEventData that = (BytesCloudEventData) o;
|
||||
return Arrays.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Arrays.hashCode(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BytesCloudEventData{" +
|
||||
"value=" + Arrays.toString(value) +
|
||||
'}';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value byte array to wrap
|
||||
* @return byte array wrapped in a {@link BytesCloudEventData}, which implements {@link CloudEventData}.
|
||||
*/
|
||||
public static BytesCloudEventData wrap(byte[] value) {
|
||||
return new BytesCloudEventData(value);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
package io.cloudevents.core.data;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An implementation of {@link CloudEventData} that wraps any POJO.
|
||||
*
|
||||
* @param <T> the type of the wrapped POJO.
|
||||
*/
|
||||
public class PojoCloudEventData<T> implements CloudEventData {
|
||||
|
||||
/**
|
||||
* Interface defining a conversion from T to byte array. This is similar to {@link java.util.function.Function}
|
||||
* but it allows checked exceptions.
|
||||
*
|
||||
* @param <T> the source type of the conversion
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ToBytes<T> {
|
||||
/**
|
||||
* @param data the POJO to convert
|
||||
* @return the serialized byte array.
|
||||
* @throws Exception when something goes wrong during the conversion.
|
||||
*/
|
||||
byte[] convert(T data) throws Exception;
|
||||
}
|
||||
|
||||
private final T value;
|
||||
private byte[] memoizedValue;
|
||||
private final ToBytes<T> mapper;
|
||||
|
||||
private PojoCloudEventData(T value, ToBytes<T> mapper) {
|
||||
this(value, null, mapper);
|
||||
}
|
||||
|
||||
private PojoCloudEventData(T value, byte[] memoizedValue, ToBytes<T> mapper) {
|
||||
Objects.requireNonNull(value);
|
||||
if (memoizedValue == null && mapper == null) {
|
||||
throw new NullPointerException("You must provide the serialized data value or a mapper");
|
||||
}
|
||||
this.value = value;
|
||||
this.memoizedValue = memoizedValue;
|
||||
this.mapper = mapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the wrapped POJO
|
||||
*/
|
||||
public T getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] toBytes() {
|
||||
if (this.memoizedValue == null) {
|
||||
try {
|
||||
this.memoizedValue = mapper.convert(this.value);
|
||||
} catch (Exception e) {
|
||||
throw CloudEventRWException.newDataConversion(e, value.getClass().toString(), "byte[]");
|
||||
}
|
||||
}
|
||||
return this.memoizedValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PojoCloudEventData<?> that = (PojoCloudEventData<?>) o;
|
||||
return Objects.equals(getValue(), that.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the provided data in a {@link PojoCloudEventData} serializable by the provided mapper.
|
||||
*
|
||||
* @param <T> The type of {@code data}
|
||||
* @param data the POJO to wrap
|
||||
* @param mapper converter from {@code data} to bytes, used to implement {@link #toBytes()}
|
||||
* @return the new {@link PojoCloudEventData}
|
||||
*/
|
||||
public static <T> PojoCloudEventData<T> wrap(T data, ToBytes<T> mapper) {
|
||||
return new PojoCloudEventData<>(data, mapper);
|
||||
}
|
||||
}
|
|
@ -18,8 +18,9 @@
|
|||
package io.cloudevents.core.extensions;
|
||||
|
||||
import io.cloudevents.CloudEventExtensions;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.extensions.impl.ExtensionUtils;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
@ -27,19 +28,30 @@ import java.util.Set;
|
|||
|
||||
/**
|
||||
* This extension supports the "Claim Check Pattern". It allows to specify a reference to a location where the event payload is stored.
|
||||
*
|
||||
* @see <a href=https://github.com/cloudevents/spec/blob/v1.0/extensions/dataref.md>https://github.com/cloudevents/spec/blob/v1.0/extensions/dataref.md</a>
|
||||
*/
|
||||
public final class DatarefExtension implements Extension {
|
||||
public final class DatarefExtension implements CloudEventExtension {
|
||||
|
||||
/**
|
||||
* The key of the {@code dataref} extension
|
||||
*/
|
||||
public static final String DATAREF = "dataref";
|
||||
|
||||
private static final Set<String> KEY_SET = Collections.unmodifiableSet(new HashSet<>(Collections.singletonList(DATAREF)));
|
||||
|
||||
private URI dataref;
|
||||
|
||||
/**
|
||||
* @return the {@code dataref} contained in this extension.
|
||||
*/
|
||||
public URI getDataref() {
|
||||
return dataref;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param dataref the uri to set as {@code dataref}.
|
||||
*/
|
||||
public void setDataref(URI dataref) {
|
||||
this.dataref = dataref;
|
||||
}
|
||||
|
@ -55,9 +67,9 @@ public final class DatarefExtension implements Extension {
|
|||
@Override
|
||||
public Object getValue(String key) {
|
||||
if (DATAREF.equals(key)) {
|
||||
return this.dataref;
|
||||
return this.dataref.toString();
|
||||
}
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass().getSimpleName(), key);
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,40 +18,57 @@
|
|||
package io.cloudevents.core.extensions;
|
||||
|
||||
import io.cloudevents.CloudEventExtensions;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.extensions.impl.ExtensionUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This extension embeds context from Distributed Tracing so that distributed systems can include traces that span an event-driven system.
|
||||
*
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md">https://github.com/cloudevents/spec/blob/master/extensions/distributed-tracing.md</a>
|
||||
* @see <a href="https://github.com/cloudevents/spec/blob/main/extensions/distributed-tracing.md">https://github.com/cloudevents/spec/blob/main/extensions/distributed-tracing.md</a>
|
||||
*/
|
||||
public final class DistributedTracingExtension implements Extension {
|
||||
public final class DistributedTracingExtension implements CloudEventExtension {
|
||||
|
||||
/**
|
||||
* The key of the {@code traceparent} extension
|
||||
*/
|
||||
public static final String TRACEPARENT = "traceparent";
|
||||
|
||||
/**
|
||||
* The key of the {@code tracestate} extension
|
||||
*/
|
||||
public static final String TRACESTATE = "tracestate";
|
||||
|
||||
private static final Set<String> KEY_SET = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(TRACEPARENT, TRACESTATE)));
|
||||
|
||||
private String traceparent;
|
||||
private String tracestate;
|
||||
|
||||
/**
|
||||
* @return the {@code traceparent} contained in this extension.
|
||||
*/
|
||||
public String getTraceparent() {
|
||||
return traceparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param traceparent the string to set as {@code traceparent}.
|
||||
*/
|
||||
public void setTraceparent(String traceparent) {
|
||||
this.traceparent = traceparent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the {@code tracestate} contained in this extension.
|
||||
*/
|
||||
public String getTracestate() {
|
||||
return tracestate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tracestate the string to set as {@code tracestate}.
|
||||
*/
|
||||
public void setTracestate(String tracestate) {
|
||||
this.tracestate = tracestate;
|
||||
}
|
||||
|
@ -76,7 +93,7 @@ public final class DistributedTracingExtension implements Extension {
|
|||
case TRACESTATE:
|
||||
return this.tracestate;
|
||||
}
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass().getSimpleName(), key);
|
||||
throw ExtensionUtils.generateInvalidKeyException(this.getClass(), key);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -93,35 +110,16 @@ public final class DistributedTracingExtension implements Extension {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((traceparent == null) ? 0
|
||||
: traceparent.hashCode());
|
||||
result = prime * result + ((tracestate == null) ? 0
|
||||
: tracestate.hashCode());
|
||||
return result;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
DistributedTracingExtension that = (DistributedTracingExtension) o;
|
||||
return Objects.equals(getTraceparent(), that.getTraceparent()) &&
|
||||
Objects.equals(getTracestate(), that.getTracestate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
DistributedTracingExtension other = (DistributedTracingExtension) obj;
|
||||
if (traceparent == null) {
|
||||
if (other.traceparent != null)
|
||||
return false;
|
||||
} else if (!traceparent.equals(other.traceparent))
|
||||
return false;
|
||||
if (tracestate == null) {
|
||||
if (other.tracestate != null)
|
||||
return false;
|
||||
} else if (!tracestate.equals(other.tracestate))
|
||||
return false;
|
||||
return true;
|
||||
public int hashCode() {
|
||||
return Objects.hash(getTraceparent(), getTracestate());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,23 @@
|
|||
|
||||
package io.cloudevents.core.extensions.impl;
|
||||
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
|
||||
/**
|
||||
* Collection of utilities to deal with materialized extensions
|
||||
*/
|
||||
public final class ExtensionUtils {
|
||||
|
||||
private ExtensionUtils() {
|
||||
}
|
||||
|
||||
public static IllegalArgumentException generateInvalidKeyException(String extensionName, String key) {
|
||||
return new IllegalArgumentException(extensionName + " doesn't expect the attribute key \"" + key + "\"");
|
||||
/**
|
||||
* @param clazz the {@link CloudEventExtension} class
|
||||
* @param key the invalid key
|
||||
* @return an {@link IllegalArgumentException} when trying to access a key of the extension not existing.
|
||||
*/
|
||||
public static IllegalArgumentException generateInvalidKeyException(Class<? extends CloudEventExtension> clazz, String key) {
|
||||
return new IllegalArgumentException(clazz.getName() + " doesn't expect the attribute key \"" + key + "\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.format;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* <p>A construct that aggregates a two-part identifier of file formats and format contents transmitted on the Internet.
|
||||
*
|
||||
* <p>The two parts of a {@code ContentType} are its <em>type</em> and a <em>subtype</em>; separated by a forward slash ({@code /}).
|
||||
*
|
||||
* <p>The constants enumerated by {@code ContentType} correspond <em>only</em> to the specialized formats supported by the Java™ SDK for CloudEvents.
|
||||
*
|
||||
* @see io.cloudevents.core.format.EventFormat
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public enum ContentType {
|
||||
|
||||
/**
|
||||
* Content type associated with the JSON event format
|
||||
*/
|
||||
JSON("application/cloudevents+json"),
|
||||
/**
|
||||
* The content type for transports sending cloudevents in the protocol buffer format.
|
||||
*/
|
||||
PROTO("application/cloudevents+protobuf"),
|
||||
/**
|
||||
* The content type for transports sending cloudevents in the compact Avro format.
|
||||
*/
|
||||
AVRO_COMPACT("application/cloudevents+avrocompact"),
|
||||
/**
|
||||
* The content type for transports sending cloudevents in XML format.
|
||||
*/
|
||||
XML("application/cloudevents+xml");
|
||||
|
||||
private String value;
|
||||
|
||||
private ContentType(String value) { this.value = value; }
|
||||
|
||||
/**
|
||||
* Return a string consisting of the slash-delimited ({@code /}) two-part identifier for this {@code enum} constant.
|
||||
*/
|
||||
public String value() { return value; }
|
||||
|
||||
/**
|
||||
* Return a string consisting of the slash-delimited ({@code /}) two-part identifier for this {@code enum} constant.
|
||||
*/
|
||||
@Override
|
||||
public String toString() { return value(); }
|
||||
|
||||
}
|
|
@ -21,7 +21,10 @@ package io.cloudevents.core.format;
|
|||
* Exception representing a deserialization error while using an {@link EventFormat}.
|
||||
*/
|
||||
public class EventDeserializationException extends RuntimeException {
|
||||
public EventDeserializationException(Throwable e) {
|
||||
super(e);
|
||||
/**
|
||||
* @param cause the cause of the exception
|
||||
*/
|
||||
public EventDeserializationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
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;
|
||||
|
@ -45,14 +47,24 @@ public interface EventFormat {
|
|||
*/
|
||||
byte[] serialize(CloudEvent event) throws EventSerializationException;
|
||||
|
||||
/**
|
||||
* Like {@link #deserialize(byte[], CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @see #deserialize(byte[], CloudEventDataMapper)
|
||||
*/
|
||||
default CloudEvent deserialize(byte[] bytes) throws EventDeserializationException {
|
||||
return this.deserialize(bytes, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize a byte array to a {@link CloudEvent}.
|
||||
*
|
||||
* @param bytes the serialized event.
|
||||
* @param bytes the serialized event.
|
||||
* @param mapper the mapper to use to map the data.
|
||||
* @return the deserialized event.
|
||||
* @throws EventDeserializationException if something goes wrong during deserialization.
|
||||
*/
|
||||
CloudEvent deserialize(byte[] bytes) throws EventDeserializationException;
|
||||
CloudEvent deserialize(byte[] bytes, CloudEventDataMapper<? extends CloudEventData> mapper) throws EventDeserializationException;
|
||||
|
||||
/**
|
||||
* @return the set of content types this event format can deserialize. These content types are used
|
||||
|
|
|
@ -21,7 +21,10 @@ package io.cloudevents.core.format;
|
|||
* Exception representing a serialization error while using an {@link EventFormat}.
|
||||
*/
|
||||
public class EventSerializationException extends RuntimeException {
|
||||
public EventSerializationException(Throwable e) {
|
||||
super(e);
|
||||
/**
|
||||
* @param cause the cause of the exception
|
||||
*/
|
||||
public EventSerializationException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,24 +18,27 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.rw.*;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class BaseCloudEvent implements CloudEvent, CloudEventReader {
|
||||
public abstract class BaseCloudEvent implements CloudEvent, CloudEventReader, CloudEventContextReader {
|
||||
|
||||
private final byte[] data;
|
||||
private final CloudEventData data;
|
||||
protected final Map<String, Object> extensions;
|
||||
|
||||
protected BaseCloudEvent(byte[] data, Map<String, Object> extensions) {
|
||||
protected BaseCloudEvent(CloudEventData data, Map<String, Object> extensions) {
|
||||
this.data = data;
|
||||
this.extensions = extensions != null ? extensions : new HashMap<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getData() {
|
||||
public CloudEventData getData() {
|
||||
return this.data;
|
||||
}
|
||||
|
||||
|
@ -49,27 +52,33 @@ public abstract class BaseCloudEvent implements CloudEvent, CloudEventReader {
|
|||
return this.extensions.keySet();
|
||||
}
|
||||
|
||||
public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> writerFactory) throws CloudEventRWException, IllegalStateException {
|
||||
CloudEventWriter<V> visitor = writerFactory.create(this.getSpecVersion());
|
||||
this.readAttributes(visitor);
|
||||
this.readExtensions(visitor);
|
||||
@Override
|
||||
public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException {
|
||||
CloudEventWriter<V> writer = writerFactory.create(this.getSpecVersion());
|
||||
this.readContext(writer);
|
||||
|
||||
if (this.data != null) {
|
||||
return visitor.end(this.data);
|
||||
return writer.end(mapper.map(this.data));
|
||||
}
|
||||
|
||||
return visitor.end();
|
||||
return writer.end();
|
||||
}
|
||||
|
||||
public void readExtensions(CloudEventExtensionsWriter visitor) throws CloudEventRWException {
|
||||
protected void readExtensions(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
// TODO to be improved
|
||||
for (Map.Entry<String, Object> entry : this.extensions.entrySet()) {
|
||||
if (entry.getValue() instanceof String) {
|
||||
visitor.setExtension(entry.getKey(), (String) entry.getValue());
|
||||
writer.withContextAttribute(entry.getKey(), (String) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Number) {
|
||||
visitor.setExtension(entry.getKey(), (Number) entry.getValue());
|
||||
writer.withContextAttribute(entry.getKey(), (Number) entry.getValue());
|
||||
} else if (entry.getValue() instanceof Boolean) {
|
||||
visitor.setExtension(entry.getKey(), (Boolean) entry.getValue());
|
||||
writer.withContextAttribute(entry.getKey(), (Boolean) entry.getValue());
|
||||
} else if (entry.getValue() instanceof URI) {
|
||||
writer.withContextAttribute(entry.getKey(), (URI) entry.getValue());
|
||||
} else if (entry.getValue() instanceof OffsetDateTime) {
|
||||
writer.withContextAttribute(entry.getKey(), (OffsetDateTime) entry.getValue());
|
||||
} else if (entry.getValue() instanceof byte[]) {
|
||||
writer.withContextAttribute(entry.getKey(), (byte[]) entry.getValue());
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
|
||||
|
|
|
@ -18,48 +18,52 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.data.BytesCloudEventData;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static io.cloudevents.core.v03.CloudEventV03.SPECVERSION;
|
||||
|
||||
public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<SELF, T>, T extends CloudEvent> implements CloudEventBuilder {
|
||||
|
||||
// This is a little trick for enabling fluency
|
||||
private final SELF self;
|
||||
|
||||
protected byte[] data;
|
||||
protected Map<String, Object> extensions;
|
||||
protected CloudEventData data;
|
||||
protected Map<String, Object> extensions = new HashMap<>();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BaseCloudEventBuilder() {
|
||||
this.self = (SELF) this;
|
||||
this.extensions = new HashMap<>();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public BaseCloudEventBuilder(CloudEvent event) {
|
||||
this.self = (SELF) this;
|
||||
public BaseCloudEventBuilder(CloudEventContext context) {
|
||||
this();
|
||||
setAttributes(context);
|
||||
}
|
||||
|
||||
public BaseCloudEventBuilder(CloudEvent event) {
|
||||
this();
|
||||
this.setAttributes(event);
|
||||
this.data = event.getData();
|
||||
this.extensions = new HashMap<>();
|
||||
for (String k : event.getExtensionNames()) {
|
||||
this.extensions.put(k, event.getExtension(k));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void setAttributes(CloudEvent event);
|
||||
protected abstract void setAttributes(CloudEventContext event);
|
||||
|
||||
//TODO builder should accept data as Object and use data codecs (that we need to implement)
|
||||
// to encode data
|
||||
|
||||
public SELF withData(byte[] data) {
|
||||
this.data = data;
|
||||
this.data = BytesCloudEventData.wrap(data);
|
||||
return this.self;
|
||||
}
|
||||
|
||||
|
@ -76,22 +80,117 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
return this.self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, String value) {
|
||||
public SELF withData(CloudEventData data) {
|
||||
this.data = data;
|
||||
return this.self;
|
||||
}
|
||||
|
||||
public SELF withData(String dataContentType, CloudEventData data) {
|
||||
withDataContentType(dataContentType);
|
||||
withData(data);
|
||||
return this.self;
|
||||
}
|
||||
|
||||
public SELF withData(String dataContentType, URI dataSchema, CloudEventData data) {
|
||||
withDataContentType(dataContentType);
|
||||
withDataSchema(dataSchema);
|
||||
withData(data);
|
||||
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);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, Number value) {
|
||||
// @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);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull String key, boolean value) {
|
||||
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 Extension extension) {
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull Boolean value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull URI value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withExtension(@Nonnull String key, @Nonnull OffsetDateTime value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventBuilder withExtension(@Nonnull String key, @Nonnull byte[] value) {
|
||||
if (!isValidExtensionName(key)) {
|
||||
throw CloudEventRWException.newInvalidExtensionName(key);
|
||||
}
|
||||
this.extensions.put(key, value);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withoutExtension(@Nonnull String key) {
|
||||
this.extensions.remove(key);
|
||||
return self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SELF withoutExtension(@Nonnull CloudEventExtension extension) {
|
||||
extension.getKeys().forEach(this::withoutExtension);
|
||||
return self;
|
||||
}
|
||||
|
||||
public SELF withExtension(@Nonnull CloudEventExtension extension) {
|
||||
for (String key : extension.getKeys()) {
|
||||
Object value = extension.getValue(key);
|
||||
if (value != null) {
|
||||
|
@ -102,22 +201,7 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setExtension(String name, String value) throws CloudEventRWException {
|
||||
this.withExtension(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtension(String name, Number value) throws CloudEventRWException {
|
||||
this.withExtension(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setExtension(String name, Boolean value) throws CloudEventRWException {
|
||||
this.withExtension(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEvent end(byte[] value) throws CloudEventRWException {
|
||||
public CloudEvent end(CloudEventData value) throws CloudEventRWException {
|
||||
this.data = value;
|
||||
return build();
|
||||
}
|
||||
|
@ -134,4 +218,34 @@ 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/main/cloudevents/spec.md#naming-conventions">attribute-naming-conventions</a>
|
||||
*/
|
||||
private static boolean isValidExtensionName(String name) {
|
||||
for (int i = 0; i < name.length(); i++) {
|
||||
if (!isValidChar(name.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isValidChar(char c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9');
|
||||
}
|
||||
|
||||
protected void requireValidAttributeWrite(String name) {
|
||||
if (name.equals(SPECVERSION)) {
|
||||
throw new IllegalArgumentException("You should not set the specversion attribute through withContextAttribute methods");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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 io.cloudevents.CloudEventContext;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public class CloudEventContextReaderAdapter implements CloudEventContextReader {
|
||||
|
||||
private final CloudEventContext event;
|
||||
|
||||
public CloudEventContextReaderAdapter(CloudEventContext event) {
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
public void readAttributes(CloudEventContextWriter writer) throws RuntimeException {
|
||||
writer.withContextAttribute("id", event.getId());
|
||||
writer.withContextAttribute("source", event.getSource());
|
||||
writer.withContextAttribute("type", event.getType());
|
||||
if (event.getDataContentType() != null) {
|
||||
writer.withContextAttribute("datacontenttype", event.getDataContentType());
|
||||
}
|
||||
if (event.getDataSchema() != null) {
|
||||
writer.withContextAttribute("dataschema", event.getDataSchema());
|
||||
}
|
||||
if (event.getSubject() != null) {
|
||||
writer.withContextAttribute("subject", event.getSubject());
|
||||
}
|
||||
if (event.getTime() != null) {
|
||||
writer.withContextAttribute("time", event.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public void readExtensions(CloudEventContextWriter writer) throws RuntimeException {
|
||||
for (String key : event.getExtensionNames()) {
|
||||
Object value = event.getExtension(key);
|
||||
if (value instanceof String) {
|
||||
writer.withContextAttribute(key, (String) value);
|
||||
} else if (value instanceof Number) {
|
||||
writer.withContextAttribute(key, (Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
writer.withContextAttribute(key, (Boolean) value);
|
||||
} else if (value instanceof URI) {
|
||||
writer.withContextAttribute(key, (URI) value);
|
||||
} else if (value instanceof OffsetDateTime) {
|
||||
writer.withContextAttribute(key, (OffsetDateTime) value);
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + key + " " + value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
this.readAttributes(writer);
|
||||
this.readExtensions(writer);
|
||||
}
|
||||
}
|
|
@ -18,64 +18,30 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.rw.*;
|
||||
|
||||
public class CloudEventReaderAdapter implements CloudEventReader {
|
||||
public class CloudEventReaderAdapter extends CloudEventContextReaderAdapter implements CloudEventReader {
|
||||
|
||||
private CloudEvent event;
|
||||
private final CloudEvent event;
|
||||
|
||||
CloudEventReaderAdapter(CloudEvent event) {
|
||||
public CloudEventReaderAdapter(CloudEvent event) {
|
||||
super(event);
|
||||
this.event = event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory) throws RuntimeException {
|
||||
public <V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory,
|
||||
CloudEventDataMapper<? extends CloudEventData> mapper) throws RuntimeException {
|
||||
CloudEventWriter<R> visitor = writerFactory.create(event.getSpecVersion());
|
||||
this.readAttributes(visitor);
|
||||
this.readExtensions(visitor);
|
||||
|
||||
if (event.getData() != null) {
|
||||
return visitor.end(event.getData());
|
||||
return visitor.end(mapper.map(event.getData()));
|
||||
}
|
||||
|
||||
return visitor.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws RuntimeException {
|
||||
writer.setAttribute("id", event.getId());
|
||||
writer.setAttribute("source", event.getSource());
|
||||
writer.setAttribute("type", event.getType());
|
||||
if (event.getDataContentType() != null) {
|
||||
writer.setAttribute("datacontenttype", event.getDataContentType());
|
||||
}
|
||||
if (event.getDataSchema() != null) {
|
||||
writer.setAttribute("dataschema", event.getDataSchema());
|
||||
}
|
||||
if (event.getSubject() != null) {
|
||||
writer.setAttribute("subject", event.getSubject());
|
||||
}
|
||||
if (event.getTime() != null) {
|
||||
writer.setAttribute("time", event.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtensions(CloudEventExtensionsWriter visitor) throws RuntimeException {
|
||||
for (String key : event.getExtensionNames()) {
|
||||
Object value = event.getExtension(key);
|
||||
if (value instanceof String) {
|
||||
visitor.setExtension(key, (String) value);
|
||||
} else if (value instanceof Number) {
|
||||
visitor.setExtension(key, (Number) value);
|
||||
} else if (value instanceof Boolean) {
|
||||
visitor.setExtension(key, (Boolean) value);
|
||||
} else {
|
||||
// This should never happen because we build that map only through our builders
|
||||
throw new IllegalStateException("Illegal value inside extensions map: " + key + " " + value);
|
||||
}
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.impl;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
final public class StringUtils {
|
||||
|
||||
private StringUtils() {
|
||||
// Prevent construction.
|
||||
}
|
||||
|
||||
public static boolean startsWithIgnoreCase(@Nonnull final String s, @Nonnull final String prefix) {
|
||||
return s.regionMatches(true /* ignoreCase */, 0, prefix, 0, prefix.length());
|
||||
}
|
||||
}
|
|
@ -21,7 +21,12 @@ package io.cloudevents.core.message;
|
|||
* One of the possible encodings of a <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#message">CloudEvent message</a>
|
||||
*/
|
||||
public enum Encoding {
|
||||
/**
|
||||
* Structured mode
|
||||
*/
|
||||
STRUCTURED,
|
||||
BINARY,
|
||||
UNKNOWN
|
||||
/**
|
||||
* Binary mode
|
||||
*/
|
||||
BINARY
|
||||
}
|
||||
|
|
|
@ -18,53 +18,51 @@
|
|||
package io.cloudevents.core.message;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.rw.*;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
/**
|
||||
* Represents a <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#message">CloudEvent message</a>.
|
||||
* Represents a <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#message">CloudEvent message</a> reader.
|
||||
* <p>
|
||||
* This class expands the {@link CloudEventReader} to define reading both binary and structured messages.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public interface MessageReader extends StructuredMessageReader, CloudEventReader {
|
||||
|
||||
/**
|
||||
* Visit the message as binary encoded event using the provided visitor factory.
|
||||
* Like {@link #read(CloudEventWriterFactory, CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @param writerFactory a factory that generates a visitor starting from the SpecVersion of the event
|
||||
* @see #read(CloudEventWriterFactory, CloudEventDataMapper)
|
||||
*/
|
||||
default <W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory) throws CloudEventRWException, IllegalStateException {
|
||||
return read(writerFactory, CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the message as binary encoded message using the provided writer factory.
|
||||
*
|
||||
* @param <W> the {@link CloudEventWriter} type
|
||||
* @param <R> the return type of the {@link CloudEventWriter}
|
||||
* @param writerFactory a factory that generates a reader starting from the {@link SpecVersion} of the event
|
||||
* @param mapper the mapper to use to map the data, if any.
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in binary encoding.
|
||||
*/
|
||||
<V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory) throws CloudEventRWException, IllegalStateException;
|
||||
<W extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<W, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* Visit the message attributes as binary encoded event using the provided visitor.
|
||||
* Read the message as structured encoded message using the provided writer
|
||||
*
|
||||
* @param writer Attributes visitor
|
||||
* @param <R> the return type of the {@link StructuredMessageWriter}
|
||||
* @param writer Structured Message writer
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in binary encoding.
|
||||
* @throws IllegalStateException if the message is not in structured encoding.
|
||||
*/
|
||||
void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* Visit the message extensions as binary encoded event using the provided visitor.
|
||||
*
|
||||
* @param visitor Extensions visitor
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in binary encoding.
|
||||
*/
|
||||
void readExtensions(CloudEventExtensionsWriter visitor) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* Visit the message as structured encoded event using the provided visitor
|
||||
*
|
||||
* @param visitor Structured Message visitor
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message is not in structured encoding.
|
||||
*/
|
||||
<T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException;
|
||||
<R> R read(StructuredMessageWriter<R> writer) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
/**
|
||||
* @return The message encoding
|
||||
|
@ -72,40 +70,56 @@ public interface MessageReader extends StructuredMessageReader, CloudEventReader
|
|||
Encoding getEncoding();
|
||||
|
||||
/**
|
||||
* Visit the event using a {@link MessageWriter}. This method allows to transcode an event from one transport to another without
|
||||
* Read the content of this object using a {@link MessageWriter}. This method allows to transcode an event from one transport to another without
|
||||
* converting it to {@link CloudEvent}. The resulting encoding will be the same as the original encoding.
|
||||
*
|
||||
* @param visitor the MessageVisitor accepting this Message
|
||||
* @return The return value of the MessageVisitor
|
||||
* @param <BW> the {@link CloudEventWriter} type
|
||||
* @param <R> the return type of both {@link CloudEventWriter} and {@link StructuredMessageWriter}
|
||||
* @param writer the {@link MessageWriter} accepting this Message
|
||||
* @return The return value of the {@link MessageWriter}
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
*/
|
||||
default <BV extends CloudEventWriter<R>, R> R visit(MessageWriter<BV, R> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
default <BW extends CloudEventWriter<R>, R> R read(MessageWriter<BW, R> writer) throws CloudEventRWException, IllegalStateException {
|
||||
switch (getEncoding()) {
|
||||
case BINARY:
|
||||
return this.read((CloudEventWriterFactory<BV, R>) visitor);
|
||||
return this.read((CloudEventWriterFactory<BW, R>) writer);
|
||||
case STRUCTURED:
|
||||
return this.read((StructuredMessageWriter<R>) visitor);
|
||||
return this.read((StructuredMessageWriter<R>) writer);
|
||||
default:
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
throw new IllegalStateException(
|
||||
"The provided Encoding doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate this message into a {@link CloudEvent} representation.
|
||||
* Like {@link #toEvent(CloudEventDataMapper)}, but with the identity {@link CloudEventDataMapper}.
|
||||
*
|
||||
* @return A {@link CloudEvent} with the contents of this message.
|
||||
* @throws CloudEventRWException if something went wrong during the visit.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
* @see #toEvent(CloudEventDataMapper)
|
||||
*/
|
||||
default CloudEvent toEvent() throws CloudEventRWException, IllegalStateException {
|
||||
return toEvent(CloudEventDataMapper.identity());
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate this message into a {@link CloudEvent} representation, mapping the data with the provided {@code mapper}.
|
||||
*
|
||||
* @param mapper the mapper to use to map the data, if any.
|
||||
* @return A {@link CloudEvent} with the contents of this message.
|
||||
* @throws CloudEventRWException if something went wrong during the read.
|
||||
* @throws IllegalStateException if the message has an unknown encoding.
|
||||
*/
|
||||
default CloudEvent toEvent(CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException {
|
||||
switch (getEncoding()) {
|
||||
case BINARY:
|
||||
return this.read(CloudEventBuilder::fromSpecVersion);
|
||||
return CloudEventUtils.toEvent(this, mapper);
|
||||
case STRUCTURED:
|
||||
return this.read(EventFormat::deserialize);
|
||||
return this.read((format, value) -> format.deserialize(value, mapper));
|
||||
default:
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
throw new IllegalStateException(
|
||||
"The provided Encoding doesn't exist. Please make sure your io.cloudevents deps versions are aligned."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -18,8 +18,8 @@
|
|||
package io.cloudevents.core.message;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.core.impl.CloudEventUtils;
|
||||
import io.cloudevents.core.message.impl.GenericStructuredMessageReader;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import io.cloudevents.rw.CloudEventWriterFactory;
|
||||
|
@ -68,7 +68,7 @@ public interface MessageWriter<CEV extends CloudEventWriter<R>, R> extends Cloud
|
|||
* @return return value at the end of the write process.
|
||||
*/
|
||||
default R writeBinary(CloudEvent event) {
|
||||
return CloudEventUtils.toVisitable(event).read(this);
|
||||
return CloudEventUtils.toReader(event).read(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,8 +18,10 @@
|
|||
package io.cloudevents.core.message;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.core.message.impl.GenericStructuredMessageReader;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
@ -32,20 +34,28 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
public interface StructuredMessageReader {
|
||||
|
||||
/**
|
||||
* @param visitor
|
||||
* Read self using the provided writer.
|
||||
*
|
||||
* @param <R> the return type of the {@link StructuredMessageWriter}
|
||||
* @param writer the writer to use to write out the message
|
||||
* @return the return value returned by {@link StructuredMessageWriter#setEvent(EventFormat, byte[])}
|
||||
* @throws CloudEventRWException If something went wrong when
|
||||
* @throws IllegalStateException If the message is not a valid structured message
|
||||
*/
|
||||
<T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException;
|
||||
<R> R read(StructuredMessageWriter<R> writer) throws CloudEventRWException, IllegalStateException;
|
||||
|
||||
default CloudEvent toEvent() throws CloudEventRWException, IllegalStateException {
|
||||
return this.read(EventFormat::deserialize);
|
||||
}
|
||||
|
||||
default CloudEvent toEvent(CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException {
|
||||
return this.read((format, value) -> format.deserialize(value, mapper));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a generic structured message from a {@link CloudEvent}
|
||||
* Create a generic structured message from a {@link CloudEvent}.
|
||||
*
|
||||
* @param event
|
||||
* @param event the event to convert to {@link StructuredMessageReader}
|
||||
* @param contentType content type to use to resolve the {@link EventFormat}
|
||||
* @return null if format was not found, otherwise returns the built message
|
||||
*/
|
||||
|
@ -54,10 +64,10 @@ public interface StructuredMessageReader {
|
|||
}
|
||||
|
||||
/**
|
||||
* Create a generic structured message from a {@link CloudEvent}
|
||||
* Create a generic structured message from a {@link CloudEvent}.
|
||||
*
|
||||
* @param event
|
||||
* @param format
|
||||
* @param event the event to convert to {@link StructuredMessageReader}
|
||||
* @param format the format to use to perform the conversion
|
||||
* @return null if format was not found, otherwise returns the built message
|
||||
*/
|
||||
static StructuredMessageReader from(CloudEvent event, EventFormat format) {
|
||||
|
|
|
@ -25,15 +25,15 @@ import javax.annotation.ParametersAreNonnullByDefault;
|
|||
/**
|
||||
* Interface to write the {@link MessageReader} content (CloudEvents attributes, extensions and payload) to a new representation structured representation.
|
||||
*
|
||||
* @param <T> return value at the end of the write process.
|
||||
* @param <R> return value at the end of the write process.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
@FunctionalInterface
|
||||
public interface StructuredMessageWriter<T> {
|
||||
public interface StructuredMessageWriter<R> {
|
||||
|
||||
/**
|
||||
* Write an event using the provided {@link EventFormat}.
|
||||
*/
|
||||
T setEvent(EventFormat format, byte[] value) throws CloudEventRWException;
|
||||
R setEvent(EventFormat format, byte[] value) throws CloudEventRWException;
|
||||
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@ import io.cloudevents.core.message.MessageReader;
|
|||
import io.cloudevents.core.message.StructuredMessageWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Base {@link MessageReader} implementation for a binary message
|
||||
*/
|
||||
public abstract class BaseBinaryMessageReader implements MessageReader {
|
||||
|
||||
@Override
|
||||
|
@ -30,7 +33,7 @@ public abstract class BaseBinaryMessageReader implements MessageReader {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
public <T> T read(StructuredMessageWriter<T> writer) throws CloudEventRWException, IllegalStateException {
|
||||
throw MessageUtils.generateWrongEncoding(Encoding.STRUCTURED, Encoding.BINARY);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,15 +17,21 @@
|
|||
|
||||
package io.cloudevents.core.message.impl;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.rw.*;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import io.cloudevents.rw.CloudEventWriterFactory;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
/**
|
||||
* This class implements a Binary {@link io.cloudevents.core.message.MessageReader}, providing common logic to most protocol bindings
|
||||
* which supports both Binary and Structured mode.
|
||||
* This class implements a Binary {@link io.cloudevents.core.message.MessageReader},
|
||||
* providing common logic to most protocol bindings which supports both Binary and Structured mode.
|
||||
* <p>
|
||||
* Content-type is handled separately using a key not prefixed with CloudEvents header prefix.
|
||||
*
|
||||
* @param <HK> Header key type
|
||||
|
@ -34,83 +40,73 @@ import java.util.function.BiConsumer;
|
|||
public abstract class BaseGenericBinaryMessageReaderImpl<HK, HV> extends BaseBinaryMessageReader {
|
||||
|
||||
private final SpecVersion version;
|
||||
private final byte[] body;
|
||||
private final CloudEventData body;
|
||||
|
||||
protected BaseGenericBinaryMessageReaderImpl(SpecVersion version, byte[] body) {
|
||||
protected BaseGenericBinaryMessageReaderImpl(SpecVersion version, CloudEventData body) {
|
||||
Objects.requireNonNull(version);
|
||||
this.version = version;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> writerFactory) throws CloudEventRWException, IllegalStateException {
|
||||
public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) throws CloudEventRWException, IllegalStateException {
|
||||
CloudEventWriter<V> visitor = writerFactory.create(this.version);
|
||||
|
||||
// Grab from headers the attributes and extensions
|
||||
// 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.setAttribute("datacontenttype", toCloudEventsValue(value));
|
||||
visitor.withContextAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
|
||||
} else if (isCloudEventsHeader(key)) {
|
||||
String name = toCloudEventsKey(key);
|
||||
if (name.equals("specversion")) {
|
||||
if (name.equals(CloudEventV1.SPECVERSION)) {
|
||||
return;
|
||||
}
|
||||
if (this.version.getAllAttributes().contains(name)) {
|
||||
visitor.setAttribute(name, toCloudEventsValue(value));
|
||||
} else {
|
||||
visitor.setExtension(name, toCloudEventsValue(value));
|
||||
}
|
||||
visitor.withContextAttribute(name, toCloudEventsValue(value));
|
||||
}
|
||||
});
|
||||
|
||||
// Set the payload
|
||||
if (this.body != null && this.body.length != 0) {
|
||||
return visitor.end(this.body);
|
||||
if (this.body != null) {
|
||||
return visitor.end(mapper.map(this.body));
|
||||
}
|
||||
|
||||
return visitor.end();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws RuntimeException {
|
||||
this.forEachHeader((key, value) -> {
|
||||
if (isContentTypeHeader(key)) {
|
||||
writer.setAttribute("datacontenttype", toCloudEventsValue(value));
|
||||
} else if (isCloudEventsHeader(key)) {
|
||||
String name = toCloudEventsKey(key);
|
||||
if (name.equals("specversion")) {
|
||||
return;
|
||||
}
|
||||
if (this.version.getAllAttributes().contains(name)) {
|
||||
writer.setAttribute(name, toCloudEventsValue(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtensions(CloudEventExtensionsWriter visitor) throws RuntimeException {
|
||||
// Grab from headers the attributes and extensions
|
||||
this.forEachHeader((key, value) -> {
|
||||
if (isCloudEventsHeader(key)) {
|
||||
String name = toCloudEventsKey(key);
|
||||
if (!this.version.getAllAttributes().contains(name)) {
|
||||
visitor.setExtension(name, toCloudEventsValue(value));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key header key
|
||||
* @return true if this header is the content type header, false otherwise
|
||||
*/
|
||||
protected abstract boolean isContentTypeHeader(HK key);
|
||||
|
||||
/**
|
||||
* @param key header key
|
||||
* @return true if this header is a CloudEvents header, false otherwise
|
||||
*/
|
||||
protected abstract boolean isCloudEventsHeader(HK key);
|
||||
|
||||
/**
|
||||
* @param key header key
|
||||
* @return the key converted to a CloudEvents context attribute/extension name
|
||||
*/
|
||||
protected abstract String toCloudEventsKey(HK key);
|
||||
|
||||
/**
|
||||
* Iterate over all the headers in the headers map.
|
||||
*
|
||||
* @param fn header consumer
|
||||
*/
|
||||
protected abstract void forEachHeader(BiConsumer<HK, HV> fn);
|
||||
|
||||
/**
|
||||
* @param value header key
|
||||
* @return the value converted to a valid CloudEvents attribute value as {@link String}.
|
||||
*/
|
||||
protected abstract String toCloudEventsValue(HV value);
|
||||
|
||||
}
|
||||
|
|
|
@ -17,13 +17,16 @@
|
|||
|
||||
package io.cloudevents.core.message.impl;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.core.message.Encoding;
|
||||
import io.cloudevents.core.message.MessageReader;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventExtensionsWriter;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
import io.cloudevents.rw.CloudEventWriter;
|
||||
import io.cloudevents.rw.CloudEventWriterFactory;
|
||||
|
||||
/**
|
||||
* Base {@link MessageReader} implementation for a structured message
|
||||
*/
|
||||
public abstract class BaseStructuredMessageReader implements MessageReader {
|
||||
|
||||
@Override
|
||||
|
@ -32,17 +35,7 @@ public abstract class BaseStructuredMessageReader implements MessageReader {
|
|||
}
|
||||
|
||||
@Override
|
||||
public <V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory) {
|
||||
throw MessageUtils.generateWrongEncoding(Encoding.BINARY, Encoding.STRUCTURED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws RuntimeException {
|
||||
throw MessageUtils.generateWrongEncoding(Encoding.BINARY, Encoding.STRUCTURED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtensions(CloudEventExtensionsWriter visitor) throws RuntimeException {
|
||||
public <V extends CloudEventWriter<R>, R> R read(CloudEventWriterFactory<V, R> writerFactory, CloudEventDataMapper<? extends CloudEventData> mapper) {
|
||||
throw MessageUtils.generateWrongEncoding(Encoding.BINARY, Encoding.STRUCTURED);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,10 +24,13 @@ import io.cloudevents.core.provider.EventFormatProvider;
|
|||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
/**
|
||||
* Generic implementation of a structured message.
|
||||
*/
|
||||
public class GenericStructuredMessageReader extends BaseStructuredMessageReader {
|
||||
|
||||
private EventFormat format;
|
||||
private byte[] payload;
|
||||
private final EventFormat format;
|
||||
private final byte[] payload;
|
||||
|
||||
public GenericStructuredMessageReader(EventFormat format, byte[] payload) {
|
||||
this.format = format;
|
||||
|
@ -35,8 +38,8 @@ public class GenericStructuredMessageReader extends BaseStructuredMessageReader
|
|||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
return visitor.setEvent(format, payload);
|
||||
public <T> T read(StructuredMessageWriter<T> writer) throws CloudEventRWException, IllegalStateException {
|
||||
return writer.setEvent(format, payload);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,6 +22,7 @@ import io.cloudevents.core.format.EventFormat;
|
|||
import io.cloudevents.core.message.Encoding;
|
||||
import io.cloudevents.core.message.MessageReader;
|
||||
import io.cloudevents.core.provider.EventFormatProvider;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
@ -29,26 +30,45 @@ import java.util.function.Supplier;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static io.cloudevents.rw.CloudEventRWException.newUnknownEncodingException;
|
||||
|
||||
/**
|
||||
* Collection of utilities useful to implement {@link MessageReader} and {@link io.cloudevents.core.message.MessageWriter} related code.
|
||||
*/
|
||||
public class MessageUtils {
|
||||
|
||||
/**
|
||||
* Common flow to parse an incoming message that could be structured or binary.
|
||||
*
|
||||
* @param contentTypeHeaderReader supplier that returns the content type header, if any
|
||||
* @param structuredMessageFactory factory to create the structured {@link MessageReader} from the provided {@link EventFormat}
|
||||
* @param specVersionHeaderReader supplier that returns the spec version header, if any
|
||||
* @param binaryMessageFactory factory to create the binary {@link MessageReader} from the provided {@link SpecVersion}
|
||||
* @return the instantiated {@link MessageReader}
|
||||
* @throws CloudEventRWException if something goes wrong while resolving the {@link SpecVersion} or if the message has unknown encoding
|
||||
*/
|
||||
public static MessageReader parseStructuredOrBinaryMessage(
|
||||
Supplier<String> contentTypeHeaderReader,
|
||||
Function<EventFormat, MessageReader> structuredMessageFactory,
|
||||
Supplier<String> specVersionHeaderReader,
|
||||
Function<SpecVersion, MessageReader> binaryMessageFactory,
|
||||
Supplier<MessageReader> unknownMessageFactory
|
||||
) {
|
||||
Function<SpecVersion, MessageReader> binaryMessageFactory
|
||||
) throws CloudEventRWException {
|
||||
// Let's try structured mode
|
||||
String ct = contentTypeHeaderReader.get();
|
||||
if (ct != null) {
|
||||
EventFormat format = EventFormatProvider.getInstance().resolveFormat(ct);
|
||||
if (format != null) {
|
||||
return structuredMessageFactory.apply(format);
|
||||
} else {
|
||||
/**
|
||||
* The format wasn't one we support, but if it's part of the
|
||||
* CloudEvent family it indicates it's a structured
|
||||
* representation that we can't interpret.
|
||||
*/
|
||||
if (ct.startsWith("application/cloudevents")) {
|
||||
throw newUnknownEncodingException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Let's try binary mode
|
||||
|
@ -57,7 +77,7 @@ public class MessageUtils {
|
|||
return binaryMessageFactory.apply(SpecVersion.parse(specVersionUnparsed));
|
||||
}
|
||||
|
||||
return unknownMessageFactory.get();
|
||||
throw newUnknownEncodingException();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -76,6 +96,11 @@ public class MessageUtils {
|
|||
.collect(Collectors.toMap(Function.identity(), headerNameMapping));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param expected the expected encoding
|
||||
* @param actual the actual encoding
|
||||
* @return a new instance of {@link IllegalStateException}.
|
||||
*/
|
||||
public static IllegalStateException generateWrongEncoding(Encoding expected, Encoding actual) {
|
||||
return new IllegalStateException("Cannot visit message as " + expected + " because the actual encoding is " + actual);
|
||||
}
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.message.impl;
|
||||
|
||||
import io.cloudevents.core.message.Encoding;
|
||||
import io.cloudevents.core.message.MessageReader;
|
||||
import io.cloudevents.core.message.StructuredMessageWriter;
|
||||
import io.cloudevents.rw.*;
|
||||
|
||||
public class UnknownEncodingMessageReader implements MessageReader {
|
||||
@Override
|
||||
public Encoding getEncoding() {
|
||||
return Encoding.UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends CloudEventWriter<V>, V> V read(CloudEventWriterFactory<T, V> writerFactory) throws CloudEventRWException, IllegalStateException {
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException {
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readExtensions(CloudEventExtensionsWriter visitor) throws CloudEventRWException {
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
|
||||
throw new IllegalStateException("Unknown encoding");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package io.cloudevents.core.provider;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.ServiceLoader;
|
||||
|
||||
/**
|
||||
* CloudEventValidatorProvider is a singleton class which loads and access CE Validator service providers on behalf of service clients.
|
||||
*/
|
||||
public class CloudEventValidatorProvider {
|
||||
|
||||
private static final CloudEventValidatorProvider cloudEventValidatorProvider = new CloudEventValidatorProvider();
|
||||
|
||||
private final Collection<CloudEventValidator> validators;
|
||||
|
||||
private CloudEventValidatorProvider() {
|
||||
final ServiceLoader<CloudEventValidator> loader = ServiceLoader.load(CloudEventValidator.class);
|
||||
this.validators = new ArrayList<>(2);
|
||||
for (CloudEventValidator cloudEventValidator : loader) {
|
||||
validators.add(cloudEventValidator);
|
||||
}
|
||||
}
|
||||
|
||||
public static CloudEventValidatorProvider getInstance() {
|
||||
return cloudEventValidatorProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* iterates through available Cloudevent validators.
|
||||
*
|
||||
* @param cloudEvent event to validate.
|
||||
*/
|
||||
public void validate(CloudEvent cloudEvent) {
|
||||
for (final CloudEventValidator validator : validators) {
|
||||
validator.validate(cloudEvent);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -17,70 +17,96 @@
|
|||
|
||||
package io.cloudevents.core.provider;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
|
||||
import io.cloudevents.core.format.ContentType;
|
||||
import io.cloudevents.core.format.EventFormat;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.HashMap;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Singleton holding the discovered {@link EventFormat} implementations through {@link ServiceLoader}.
|
||||
* Singleton holding the discovered {@link EventFormat} implementations through
|
||||
* {@link ServiceLoader}.
|
||||
* <p>
|
||||
* You can resolve an event format using {@code EventFormatProvider.getInstance().resolveFormat(contentType)}.
|
||||
* You can resolve an event format using
|
||||
* {@code EventFormatProvider.getInstance().resolveFormat(contentType)}.
|
||||
* <p>
|
||||
* You can programmatically add a new {@link EventFormat} implementation using {@link #registerFormat(EventFormat)}.
|
||||
* You can programmatically add a new {@link EventFormat} implementation using
|
||||
* {@link #registerFormat(EventFormat)}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class EventFormatProvider {
|
||||
|
||||
private static class SingletonContainer {
|
||||
private final static EventFormatProvider INSTANCE = new EventFormatProvider();
|
||||
}
|
||||
private static class SingletonContainer {
|
||||
private final static EventFormatProvider INSTANCE = new EventFormatProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return instance of {@link EventFormatProvider}
|
||||
*/
|
||||
public static EventFormatProvider getInstance() {
|
||||
return EventFormatProvider.SingletonContainer.INSTANCE;
|
||||
}
|
||||
/**
|
||||
* @return instance of {@link EventFormatProvider}
|
||||
*/
|
||||
public static EventFormatProvider getInstance() {
|
||||
return EventFormatProvider.SingletonContainer.INSTANCE;
|
||||
}
|
||||
|
||||
private final HashMap<String, EventFormat> formats;
|
||||
private final HashMap<String, EventFormat> formats;
|
||||
|
||||
private EventFormatProvider() {
|
||||
this.formats = new HashMap<>();
|
||||
private EventFormatProvider() {
|
||||
this.formats = new HashMap<>();
|
||||
|
||||
StreamSupport.stream(
|
||||
ServiceLoader.load(EventFormat.class).spliterator(),
|
||||
false
|
||||
).forEach(this::registerFormat);
|
||||
}
|
||||
StreamSupport.stream(ServiceLoader.load(EventFormat.class).spliterator(), false)
|
||||
.forEach(this::registerFormat);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new {@link EventFormat} programmatically.
|
||||
*
|
||||
* @param format the new format to register
|
||||
*/
|
||||
public void registerFormat(EventFormat format) {
|
||||
for (String k : format.deserializableContentTypes()) {
|
||||
this.formats.put(k, format);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Register a new {@link EventFormat} programmatically.
|
||||
*
|
||||
* @param format the new format to register
|
||||
*/
|
||||
public void registerFormat(EventFormat format) {
|
||||
for (String k : format.deserializableContentTypes()) {
|
||||
this.formats.put(k, format);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(String contentType) {
|
||||
int i = contentType.indexOf(';');
|
||||
if (i != -1) {
|
||||
contentType = contentType.substring(0, i);
|
||||
}
|
||||
return this.formats.get(contentType);
|
||||
}
|
||||
/**
|
||||
* Enumerate the supported content types.
|
||||
*
|
||||
* @return an alphabetically sorted list of content types
|
||||
*/
|
||||
public Set<String> getContentTypes() {
|
||||
Set<String> types = new TreeSet<>();
|
||||
types.addAll(this.formats.keySet());
|
||||
return types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(String contentType) {
|
||||
int i = contentType.indexOf(';');
|
||||
if (i != -1) {
|
||||
contentType = contentType.substring(0, i);
|
||||
}
|
||||
return this.formats.get(contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an event format starting from the content type.
|
||||
*
|
||||
* @param contentType the content type to resolve the event format
|
||||
* @return null if no format was found for the provided content type
|
||||
*/
|
||||
@Nullable
|
||||
public EventFormat resolveFormat(ContentType contentType) {
|
||||
return this.formats.get(contentType.value());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
package io.cloudevents.core.provider;
|
||||
|
||||
import io.cloudevents.CloudEventExtensions;
|
||||
import io.cloudevents.Extension;
|
||||
import io.cloudevents.CloudEventExtension;
|
||||
import io.cloudevents.core.extensions.DatarefExtension;
|
||||
import io.cloudevents.core.extensions.DistributedTracingExtension;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
|
@ -30,7 +30,7 @@ import java.util.function.Supplier;
|
|||
/**
|
||||
* Singleton to materialize CloudEvent extensions as POJOs.
|
||||
* <p>
|
||||
* You can materialize an {@link Extension} POJO with {@code ExtensionProvider.getInstance().parseExtension(DistributedTracingExtension.class, event)}.
|
||||
* You can materialize an {@link CloudEventExtension} POJO with {@code ExtensionProvider.getInstance().parseExtension(DistributedTracingExtension.class, event)}.
|
||||
*/
|
||||
@ParametersAreNonnullByDefault
|
||||
public final class ExtensionProvider {
|
||||
|
@ -39,6 +39,9 @@ public final class ExtensionProvider {
|
|||
private static final ExtensionProvider INSTANCE = new ExtensionProvider();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return instance of {@link ExtensionProvider}
|
||||
*/
|
||||
public static ExtensionProvider getInstance() {
|
||||
return SingletonContainer.INSTANCE;
|
||||
}
|
||||
|
@ -55,27 +58,28 @@ public final class ExtensionProvider {
|
|||
/**
|
||||
* Register a new extension type.
|
||||
*
|
||||
* @param extensionClass the class implementing {@link Extension}
|
||||
* @param factory the empty arguments factory
|
||||
* @param <T> the type of the extension
|
||||
* @param <T> the type of the extension
|
||||
* @param extensionClass the class implementing {@link CloudEventExtension}
|
||||
* @param factory the empty arguments factory
|
||||
*/
|
||||
public <T extends Extension> void registerExtension(Class<T> extensionClass, Supplier<T> factory) {
|
||||
public <T extends CloudEventExtension> void registerExtension(Class<T> extensionClass, Supplier<T> factory) {
|
||||
this.extensionFactories.put(extensionClass, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an extension from the {@link CloudEventExtensions}, materializing the corresponding POJO.
|
||||
*
|
||||
* @param extensionClass the class implementing {@link Extension}
|
||||
* @param eventExtensions the event extensions to read
|
||||
* @param <T> the type of the extension
|
||||
* @param extensionClass the class implementing {@link CloudEventExtension}
|
||||
* @param eventExtensions the event extensions to read
|
||||
* @return the parsed extension
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
@Nullable
|
||||
public <T extends Extension> T parseExtension(Class<T> extensionClass, CloudEventExtensions eventExtensions) {
|
||||
public <T extends CloudEventExtension> T parseExtension(Class<T> extensionClass, CloudEventExtensions eventExtensions) {
|
||||
Supplier<?> factory = extensionFactories.get(extensionClass);
|
||||
if (factory != null) {
|
||||
Extension ext = (Extension) factory.get();
|
||||
CloudEventExtension ext = (CloudEventExtension) factory.get();
|
||||
ext.readFrom(eventExtensions);
|
||||
return (T) ext;
|
||||
}
|
||||
|
|
|
@ -16,16 +16,22 @@
|
|||
*/
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.core.impl.CloudEventUtils;
|
||||
import io.cloudevents.core.provider.CloudEventValidatorProvider;
|
||||
import io.cloudevents.core.v1.CloudEventV1;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import static io.cloudevents.core.v03.CloudEventV03.*;
|
||||
|
||||
/**
|
||||
* CloudEvent V0.3 builder.
|
||||
|
@ -38,7 +44,7 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
private String id;
|
||||
private URI source;
|
||||
private String type;
|
||||
private ZonedDateTime time;
|
||||
private OffsetDateTime time;
|
||||
private URI schemaurl;
|
||||
private String datacontenttype;
|
||||
private String subject;
|
||||
|
@ -51,12 +57,17 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
super(event);
|
||||
}
|
||||
|
||||
public CloudEventBuilder(io.cloudevents.CloudEventContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setAttributes(io.cloudevents.CloudEvent event) {
|
||||
protected void setAttributes(io.cloudevents.CloudEventContext event) {
|
||||
CloudEventContextReader contextReader = CloudEventUtils.toContextReader(event);
|
||||
if (event.getSpecVersion() == SpecVersion.V03) {
|
||||
CloudEventUtils.toVisitable(event).readAttributes(this);
|
||||
contextReader.readContext(this);
|
||||
} else {
|
||||
CloudEventUtils.toVisitable(event).readAttributes(new V1ToV03AttributesConverter(this));
|
||||
contextReader.readContext(new V1ToV03AttributesConverter(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,7 +86,7 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
return this;
|
||||
}
|
||||
|
||||
public CloudEventBuilder withTime(ZonedDateTime time) {
|
||||
public CloudEventBuilder withTime(OffsetDateTime time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
@ -113,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
|
||||
|
@ -133,69 +151,166 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
}
|
||||
|
||||
// Message impl
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
withId(value);
|
||||
return;
|
||||
case "source":
|
||||
return this;
|
||||
case SOURCE:
|
||||
try {
|
||||
withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return;
|
||||
case "type":
|
||||
return this;
|
||||
case TYPE:
|
||||
withType(value);
|
||||
return;
|
||||
case "datacontenttype":
|
||||
return this;
|
||||
case DATACONTENTTYPE:
|
||||
withDataContentType(value);
|
||||
return;
|
||||
case "datacontentencoding":
|
||||
return this;
|
||||
case DATACONTENTENCODING:
|
||||
// No-op, this information is not saved in the event because it's useful only for parsing
|
||||
return;
|
||||
case "schemaurl":
|
||||
return this;
|
||||
case SCHEMAURL:
|
||||
try {
|
||||
withSchemaUrl(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("schemaurl", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SCHEMAURL, value, e);
|
||||
}
|
||||
return;
|
||||
case "subject":
|
||||
return this;
|
||||
case SUBJECT:
|
||||
withSubject(value);
|
||||
return;
|
||||
case "time":
|
||||
try {
|
||||
withTime(Time.parseTime(value));
|
||||
} catch (DateTimeParseException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
|
||||
}
|
||||
return;
|
||||
return this;
|
||||
case TIME:
|
||||
withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
withSource(value);
|
||||
return;
|
||||
case "schemaurl":
|
||||
return this;
|
||||
case SCHEMAURL:
|
||||
withDataSchema(value);
|
||||
return;
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, ZonedDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
withTime(value);
|
||||
return;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
withTime(value);
|
||||
return this;
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
|
||||
{
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, byte[] value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case DATACONTENTENCODING:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, byte[].class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, ZonedDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,15 +16,15 @@
|
|||
*/
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.impl.BaseCloudEvent;
|
||||
import io.cloudevents.lang.Nullable;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -36,18 +36,63 @@ import java.util.Objects;
|
|||
*/
|
||||
public final class CloudEventV03 extends BaseCloudEvent {
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#id">id</a> attribute
|
||||
*/
|
||||
public final static String ID = "id";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#source">source</a> attribute
|
||||
*/
|
||||
public final static String SOURCE = "source";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#specversion">specversion</a> attribute
|
||||
*/
|
||||
public final static String SPECVERSION = "specversion";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#type">type</a> attribute
|
||||
*/
|
||||
public final static String TYPE = "type";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#time">time</a> attribute
|
||||
*/
|
||||
public final static String TIME = "time";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#schemaurl">schemaurl</a> attribute
|
||||
*/
|
||||
public final static String SCHEMAURL = "schemaurl";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#datacontenttype">datacontenttype</a> attribute
|
||||
*/
|
||||
public final static String DATACONTENTTYPE = "datacontenttype";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#datacontentencoding">datacontentencoding</a> attribute
|
||||
*/
|
||||
public final static String DATACONTENTENCODING = "datacontentencoding";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v0.3/spec.md#subject">subject</a> attribute
|
||||
*/
|
||||
public final static String SUBJECT = "subject";
|
||||
|
||||
private final String id;
|
||||
private final URI source;
|
||||
private final String type;
|
||||
private final String datacontenttype;
|
||||
private final URI schemaurl;
|
||||
private final String subject;
|
||||
private final ZonedDateTime time;
|
||||
private final OffsetDateTime time;
|
||||
|
||||
public CloudEventV03(String id, URI source, String type,
|
||||
ZonedDateTime time, URI schemaurl,
|
||||
OffsetDateTime time, URI schemaurl,
|
||||
String datacontenttype, String subject,
|
||||
byte[] data, Map<String, Object> extensions) {
|
||||
CloudEventData data, Map<String, Object> extensions) {
|
||||
super(data, extensions);
|
||||
|
||||
this.id = id;
|
||||
|
@ -93,71 +138,75 @@ public final class CloudEventV03 extends BaseCloudEvent {
|
|||
return subject;
|
||||
}
|
||||
|
||||
public ZonedDateTime getTime() {
|
||||
public OffsetDateTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String attributeName) {
|
||||
switch (attributeName) {
|
||||
case "specversion":
|
||||
case SPECVERSION:
|
||||
return getSpecVersion();
|
||||
case "id":
|
||||
case ID:
|
||||
return this.id;
|
||||
case "source":
|
||||
case SOURCE:
|
||||
return this.source;
|
||||
case "type":
|
||||
case TYPE:
|
||||
return this.type;
|
||||
case "datacontenttype":
|
||||
case DATACONTENTTYPE:
|
||||
return this.datacontenttype;
|
||||
case "schemaurl":
|
||||
case SCHEMAURL:
|
||||
return this.schemaurl;
|
||||
case "subject":
|
||||
case SUBJECT:
|
||||
return this.subject;
|
||||
case "time":
|
||||
case TIME:
|
||||
return this.time;
|
||||
case DATACONTENTENCODING:
|
||||
// We don't save datacontentencoding, but the attribute name is valid, hence we just return always null
|
||||
return null;
|
||||
}
|
||||
throw new IllegalArgumentException("Spec version v0.3 doesn't have attribute named " + attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.ID.name().toLowerCase(),
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
writer.withContextAttribute(
|
||||
ID,
|
||||
this.id
|
||||
);
|
||||
writer.setAttribute(
|
||||
ContextAttributes.SOURCE.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
SOURCE,
|
||||
this.source
|
||||
);
|
||||
writer.setAttribute(
|
||||
ContextAttributes.TYPE.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
TYPE,
|
||||
this.type
|
||||
);
|
||||
if (this.datacontenttype != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.DATACONTENTTYPE.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
DATACONTENTTYPE,
|
||||
this.datacontenttype
|
||||
);
|
||||
}
|
||||
if (this.schemaurl != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.SCHEMAURL.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
SCHEMAURL,
|
||||
this.schemaurl
|
||||
);
|
||||
}
|
||||
if (this.subject != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.SUBJECT.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
SUBJECT,
|
||||
this.subject
|
||||
);
|
||||
}
|
||||
if (this.time != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.TIME.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
TIME,
|
||||
this.time
|
||||
);
|
||||
}
|
||||
this.readExtensions(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -172,13 +221,13 @@ public final class CloudEventV03 extends BaseCloudEvent {
|
|||
Objects.equals(schemaurl, that.schemaurl) &&
|
||||
Objects.equals(getSubject(), that.getSubject()) &&
|
||||
Objects.equals(getTime(), that.getTime()) &&
|
||||
Arrays.equals(getData(), that.getData()) &&
|
||||
Objects.equals(getData(), that.getData()) &&
|
||||
Objects.equals(this.extensions, that.extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getId(), getSource(), getType(), datacontenttype, schemaurl, getSubject(), getTime(), Arrays.hashCode(getData()), this.extensions);
|
||||
return Objects.hash(getId(), getSource(), getType(), datacontenttype, schemaurl, getSubject(), getTime(), getData(), this.extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,12 +236,12 @@ public final class CloudEventV03 extends BaseCloudEvent {
|
|||
"id='" + id + '\'' +
|
||||
", source=" + source +
|
||||
", type='" + type + '\'' +
|
||||
", datacontenttype='" + datacontenttype + '\'' +
|
||||
", schemaurl=" + schemaurl +
|
||||
", subject='" + subject + '\'' +
|
||||
", time=" + time +
|
||||
", data=" + Arrays.toString(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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
/*
|
||||
* Copyright 2018-Present The CloudEvents Authors
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* <p>
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* <p>
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* The specification reserved words: the context attributes
|
||||
*
|
||||
* @author fabiojose
|
||||
*/
|
||||
public enum ContextAttributes {
|
||||
ID,
|
||||
SOURCE,
|
||||
SPECVERSION,
|
||||
TYPE,
|
||||
TIME,
|
||||
SCHEMAURL,
|
||||
DATACONTENTTYPE,
|
||||
DATACONTENTENCODING,
|
||||
SUBJECT;
|
||||
public static final Set<String> VALUES =
|
||||
Arrays.stream(ContextAttributes.values())
|
||||
.map(Enum::name)
|
||||
.map(String::toLowerCase)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
public static ContextAttributes parse(String value) {
|
||||
return ContextAttributes.valueOf(value.toUpperCase());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name().toLowerCase();
|
||||
}
|
||||
}
|
|
@ -17,16 +17,17 @@
|
|||
|
||||
package io.cloudevents.core.v03;
|
||||
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
class V1ToV03AttributesConverter implements CloudEventAttributesWriter {
|
||||
import static io.cloudevents.core.v1.CloudEventV1.*;
|
||||
|
||||
class V1ToV03AttributesConverter implements CloudEventContextWriter {
|
||||
|
||||
private final CloudEventBuilder builder;
|
||||
|
||||
|
@ -35,64 +36,114 @@ class V1ToV03AttributesConverter implements CloudEventAttributesWriter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
builder.withId(value);
|
||||
return;
|
||||
case "source":
|
||||
return this;
|
||||
case SOURCE:
|
||||
try {
|
||||
builder.withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return;
|
||||
case "type":
|
||||
return this;
|
||||
case TYPE:
|
||||
builder.withType(value);
|
||||
return;
|
||||
case "datacontenttype":
|
||||
return this;
|
||||
case DATACONTENTTYPE:
|
||||
builder.withDataContentType(value);
|
||||
return;
|
||||
case "dataschema":
|
||||
return this;
|
||||
case DATASCHEMA:
|
||||
try {
|
||||
builder.withSchemaUrl(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(DATASCHEMA, value, e);
|
||||
}
|
||||
return;
|
||||
case "subject":
|
||||
return this;
|
||||
case SUBJECT:
|
||||
builder.withSubject(value);
|
||||
return;
|
||||
case "time":
|
||||
try {
|
||||
builder.withTime(Time.parseTime(value));
|
||||
} catch (DateTimeParseException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
|
||||
}
|
||||
return;
|
||||
return this;
|
||||
case TIME:
|
||||
builder.withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
builder.withSource(value);
|
||||
return;
|
||||
case "dataschema":
|
||||
return this;
|
||||
case DATASCHEMA:
|
||||
builder.withSchemaUrl(value);
|
||||
return;
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, ZonedDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
builder.withTime(value);
|
||||
return;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
builder.withTime(value);
|
||||
return this;
|
||||
case SOURCE:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, ZonedDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,15 +19,20 @@ package io.cloudevents.core.v1;
|
|||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.CloudEventUtils;
|
||||
import io.cloudevents.core.impl.BaseCloudEventBuilder;
|
||||
import io.cloudevents.core.impl.CloudEventUtils;
|
||||
import io.cloudevents.core.provider.CloudEventValidatorProvider;
|
||||
import io.cloudevents.core.validator.CloudEventValidator;
|
||||
import io.cloudevents.rw.CloudEventContextReader;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
import static io.cloudevents.core.v1.CloudEventV1.*;
|
||||
|
||||
/**
|
||||
* CloudEvent V1.0 builder.
|
||||
|
@ -43,7 +48,7 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
private String datacontenttype;
|
||||
private URI dataschema;
|
||||
private String subject;
|
||||
private ZonedDateTime time;
|
||||
private OffsetDateTime time;
|
||||
|
||||
public CloudEventBuilder() {
|
||||
super();
|
||||
|
@ -53,12 +58,17 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
super(event);
|
||||
}
|
||||
|
||||
public CloudEventBuilder(io.cloudevents.CloudEventContext context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setAttributes(io.cloudevents.CloudEvent event) {
|
||||
protected void setAttributes(io.cloudevents.CloudEventContext event) {
|
||||
CloudEventContextReader contextReader = CloudEventUtils.toContextReader(event);
|
||||
if (event.getSpecVersion() == SpecVersion.V1) {
|
||||
CloudEventUtils.toVisitable(event).readAttributes(this);
|
||||
contextReader.readContext(this);
|
||||
} else {
|
||||
CloudEventUtils.toVisitable(event).readAttributes(new V03ToV1AttributesConverter(this));
|
||||
contextReader.readContext(new V03ToV1AttributesConverter(this));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,7 +104,7 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
return this;
|
||||
}
|
||||
|
||||
public CloudEventBuilder withTime(ZonedDateTime time) {
|
||||
public CloudEventBuilder withTime(OffsetDateTime time) {
|
||||
this.time = time;
|
||||
return this;
|
||||
}
|
||||
|
@ -102,16 +112,24 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
@Override
|
||||
public CloudEvent build() {
|
||||
if (id == null) {
|
||||
throw createMissingAttributeException("id");
|
||||
throw createMissingAttributeException(ID);
|
||||
}
|
||||
if (source == null) {
|
||||
throw createMissingAttributeException("source");
|
||||
throw createMissingAttributeException(SOURCE);
|
||||
}
|
||||
if (type == null) {
|
||||
throw createMissingAttributeException("type");
|
||||
throw createMissingAttributeException(TYPE);
|
||||
}
|
||||
if (subject != null && subject.isEmpty()) {
|
||||
throw createEmptyAttributeException(("subject"));
|
||||
}
|
||||
|
||||
return new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions);
|
||||
CloudEvent cloudEvent = new CloudEventV1(id, source, type, datacontenttype, dataschema, subject, time, this.data, this.extensions);
|
||||
|
||||
final CloudEventValidatorProvider validator = CloudEventValidatorProvider.getInstance();
|
||||
validator.validate(cloudEvent);
|
||||
|
||||
return cloudEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,64 +150,157 @@ public final class CloudEventBuilder extends BaseCloudEventBuilder<CloudEventBui
|
|||
// Message impl
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
withId(value);
|
||||
return;
|
||||
case "source":
|
||||
return this;
|
||||
case SOURCE:
|
||||
try {
|
||||
withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return;
|
||||
case "type":
|
||||
return this;
|
||||
case TYPE:
|
||||
withType(value);
|
||||
return;
|
||||
case "datacontenttype":
|
||||
return this;
|
||||
case DATACONTENTTYPE:
|
||||
withDataContentType(value);
|
||||
return;
|
||||
case "dataschema":
|
||||
return this;
|
||||
case DATASCHEMA:
|
||||
try {
|
||||
withDataSchema(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(DATASCHEMA, value, e);
|
||||
}
|
||||
return;
|
||||
case "subject":
|
||||
return this;
|
||||
case SUBJECT:
|
||||
withSubject(value);
|
||||
return;
|
||||
case "time":
|
||||
try {
|
||||
withTime(Time.parseTime(value));
|
||||
} catch (DateTimeParseException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
|
||||
}
|
||||
return;
|
||||
return this;
|
||||
case TIME:
|
||||
withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
withSource(value);
|
||||
return;
|
||||
case "dataschema":
|
||||
return this;
|
||||
case DATASCHEMA:
|
||||
withDataSchema(value);
|
||||
return;
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, ZonedDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
withTime(value);
|
||||
return;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
withTime(value);
|
||||
return this;
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Integer value) throws CloudEventRWException
|
||||
{
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Integer.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, byte[] value)
|
||||
throws CloudEventRWException {
|
||||
requireValidAttributeWrite(name);
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case DATASCHEMA:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case SOURCE:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, byte[].class);
|
||||
default:
|
||||
withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, ZonedDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
*/
|
||||
package io.cloudevents.core.v1;
|
||||
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.SpecVersion;
|
||||
import io.cloudevents.core.impl.BaseCloudEvent;
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
@ -35,18 +35,58 @@ import java.util.Objects;
|
|||
*/
|
||||
public final class CloudEventV1 extends BaseCloudEvent {
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#id">id</a> attribute
|
||||
*/
|
||||
public final static String ID = "id";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#source">source</a> attribute
|
||||
*/
|
||||
public final static String SOURCE = "source";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#specversion">specversion</a> attribute
|
||||
*/
|
||||
public final static String SPECVERSION = "specversion";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#type">type</a> attribute
|
||||
*/
|
||||
public final static String TYPE = "type";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#time">time</a> attribute
|
||||
*/
|
||||
public final static String TIME = "time";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#dataschema">dataschema</a> attribute
|
||||
*/
|
||||
public final static String DATASCHEMA = "dataschema";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#datacontenttype">datacontenttype</a> attribute
|
||||
*/
|
||||
public final static String DATACONTENTTYPE = "datacontenttype";
|
||||
|
||||
/**
|
||||
* The name of the <a href="https://github.com/cloudevents/spec/blob/v1.0/spec.md#subject">subject</a> attribute
|
||||
*/
|
||||
public final static String SUBJECT = "subject";
|
||||
|
||||
private final String id;
|
||||
private final URI source;
|
||||
private final String type;
|
||||
private final String datacontenttype;
|
||||
private final URI dataschema;
|
||||
private final String subject;
|
||||
private final ZonedDateTime time;
|
||||
private final OffsetDateTime time;
|
||||
|
||||
public CloudEventV1(String id, URI source,
|
||||
String type, String datacontenttype,
|
||||
URI dataschema, String subject, ZonedDateTime time,
|
||||
byte[] data, Map<String, Object> extensions) {
|
||||
URI dataschema, String subject, OffsetDateTime time,
|
||||
CloudEventData data, Map<String, Object> extensions) {
|
||||
super(data, extensions);
|
||||
|
||||
this.id = id;
|
||||
|
@ -88,71 +128,72 @@ public final class CloudEventV1 extends BaseCloudEvent {
|
|||
return subject;
|
||||
}
|
||||
|
||||
public ZonedDateTime getTime() {
|
||||
public OffsetDateTime getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String attributeName) {
|
||||
switch (attributeName) {
|
||||
case "specversion":
|
||||
case SPECVERSION:
|
||||
return getSpecVersion();
|
||||
case "id":
|
||||
case ID:
|
||||
return this.id;
|
||||
case "source":
|
||||
case SOURCE:
|
||||
return this.source;
|
||||
case "type":
|
||||
case TYPE:
|
||||
return this.type;
|
||||
case "datacontenttype":
|
||||
case DATACONTENTTYPE:
|
||||
return this.datacontenttype;
|
||||
case "dataschema":
|
||||
case DATASCHEMA:
|
||||
return this.dataschema;
|
||||
case "subject":
|
||||
case SUBJECT:
|
||||
return this.subject;
|
||||
case "time":
|
||||
case TIME:
|
||||
return this.time;
|
||||
}
|
||||
throw new IllegalArgumentException("Spec version v1 doesn't have attribute named " + attributeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.ID.name().toLowerCase(),
|
||||
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
|
||||
writer.withContextAttribute(
|
||||
ID,
|
||||
this.id
|
||||
);
|
||||
writer.setAttribute(
|
||||
ContextAttributes.SOURCE.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
SOURCE,
|
||||
this.source
|
||||
);
|
||||
writer.setAttribute(
|
||||
ContextAttributes.TYPE.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
TYPE,
|
||||
this.type
|
||||
);
|
||||
if (this.datacontenttype != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.DATACONTENTTYPE.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
DATACONTENTTYPE,
|
||||
this.datacontenttype
|
||||
);
|
||||
}
|
||||
if (this.dataschema != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.DATASCHEMA.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
DATASCHEMA,
|
||||
this.dataschema
|
||||
);
|
||||
}
|
||||
if (this.subject != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.SUBJECT.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
SUBJECT,
|
||||
this.subject
|
||||
);
|
||||
}
|
||||
if (this.time != null) {
|
||||
writer.setAttribute(
|
||||
ContextAttributes.TIME.name().toLowerCase(),
|
||||
writer.withContextAttribute(
|
||||
TIME,
|
||||
this.time
|
||||
);
|
||||
}
|
||||
this.readExtensions(writer);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -167,13 +208,13 @@ public final class CloudEventV1 extends BaseCloudEvent {
|
|||
Objects.equals(dataschema, that.getDataSchema()) &&
|
||||
Objects.equals(getSubject(), that.getSubject()) &&
|
||||
Objects.equals(getTime(), that.getTime()) &&
|
||||
Arrays.equals(getData(), that.getData()) &&
|
||||
Objects.equals(getData(), that.getData()) &&
|
||||
Objects.equals(this.extensions, that.extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getId(), getSource(), getType(), datacontenttype, dataschema, getSubject(), getTime(), Arrays.hashCode(getData()), this.extensions);
|
||||
return Objects.hash(getId(), getSource(), getType(), datacontenttype, dataschema, getSubject(), getTime(), getData(), this.extensions);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -182,11 +223,11 @@ public final class CloudEventV1 extends BaseCloudEvent {
|
|||
"id='" + id + '\'' +
|
||||
", source=" + source +
|
||||
", type='" + type + '\'' +
|
||||
", datacontenttype='" + datacontenttype + '\'' +
|
||||
", dataschema=" + dataschema +
|
||||
", subject='" + subject + '\'' +
|
||||
", time=" + time +
|
||||
", data=" + Arrays.toString(getData()) +
|
||||
((datacontenttype != null) ? ", datacontenttype='" + datacontenttype + '\'' : "") +
|
||||
((dataschema != null) ? ", dataschema=" + dataschema : "") +
|
||||
((subject != null) ? ", subject='" + subject + '\'' : "") +
|
||||
((time != null) ? ", time=" + time : "") +
|
||||
((getData() != null) ? ", data=" + getData() : "") +
|
||||
", extensions=" + this.extensions +
|
||||
'}';
|
||||
}
|
||||
|
|
|
@ -17,16 +17,17 @@
|
|||
|
||||
package io.cloudevents.core.v1;
|
||||
|
||||
import io.cloudevents.rw.CloudEventAttributesWriter;
|
||||
import io.cloudevents.rw.CloudEventContextWriter;
|
||||
import io.cloudevents.rw.CloudEventRWException;
|
||||
import io.cloudevents.types.Time;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
class V03ToV1AttributesConverter implements CloudEventAttributesWriter {
|
||||
import static io.cloudevents.core.v03.CloudEventV03.*;
|
||||
|
||||
class V03ToV1AttributesConverter implements CloudEventContextWriter {
|
||||
|
||||
private final CloudEventBuilder builder;
|
||||
|
||||
|
@ -35,64 +36,114 @@ class V03ToV1AttributesConverter implements CloudEventAttributesWriter {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, String value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "id":
|
||||
case ID:
|
||||
builder.withId(value);
|
||||
return;
|
||||
case "source":
|
||||
return this;
|
||||
case SOURCE:
|
||||
try {
|
||||
builder.withSource(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
|
||||
}
|
||||
return;
|
||||
case "type":
|
||||
return this;
|
||||
case TYPE:
|
||||
builder.withType(value);
|
||||
return;
|
||||
case "datacontenttype":
|
||||
return this;
|
||||
case DATACONTENTTYPE:
|
||||
builder.withDataContentType(value);
|
||||
return;
|
||||
case "schemaurl":
|
||||
return this;
|
||||
case SCHEMAURL:
|
||||
try {
|
||||
builder.withDataSchema(new URI(value));
|
||||
} catch (URISyntaxException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
|
||||
throw CloudEventRWException.newInvalidAttributeValue(SCHEMAURL, value, e);
|
||||
}
|
||||
return;
|
||||
case "subject":
|
||||
return this;
|
||||
case SUBJECT:
|
||||
builder.withSubject(value);
|
||||
return;
|
||||
case "time":
|
||||
try {
|
||||
builder.withTime(Time.parseTime(value));
|
||||
} catch (DateTimeParseException e) {
|
||||
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
|
||||
}
|
||||
return;
|
||||
return this;
|
||||
case TIME:
|
||||
builder.withTime(Time.parseTime(TIME, value));
|
||||
return this;
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeName(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, URI value) throws CloudEventRWException {
|
||||
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case "source":
|
||||
case SOURCE:
|
||||
builder.withSource(value);
|
||||
return;
|
||||
case "schemaurl":
|
||||
return this;
|
||||
case SCHEMAURL:
|
||||
builder.withDataSchema(value);
|
||||
return;
|
||||
return this;
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
case TIME:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, ZonedDateTime value) throws CloudEventRWException {
|
||||
if ("time".equals(name)) {
|
||||
builder.withTime(value);
|
||||
return;
|
||||
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
builder.withTime(value);
|
||||
return this;
|
||||
case SOURCE:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, OffsetDateTime.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Number.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
|
||||
switch (name) {
|
||||
case TIME:
|
||||
case SOURCE:
|
||||
case SCHEMAURL:
|
||||
case ID:
|
||||
case TYPE:
|
||||
case DATACONTENTTYPE:
|
||||
case SUBJECT:
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, Boolean.class);
|
||||
default:
|
||||
builder.withExtension(name, value);
|
||||
return this;
|
||||
}
|
||||
throw CloudEventRWException.newInvalidAttributeType(name, ZonedDateTime.class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,29 +14,20 @@
|
|||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package io.cloudevents.core.impl;
|
||||
package io.cloudevents.core.validator;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.rw.CloudEventReader;
|
||||
|
||||
public final class CloudEventUtils {
|
||||
|
||||
private CloudEventUtils() {}
|
||||
/**
|
||||
* @author Vinay Bhat
|
||||
* Interface which defines validation for CloudEvents attributes and extensions.
|
||||
*/
|
||||
public interface CloudEventValidator {
|
||||
|
||||
/**
|
||||
* Convert a {@link CloudEvent} to a {@link CloudEventReader}. This method provides a default implementation
|
||||
* for CloudEvent that doesn't implement CloudEventVisitable
|
||||
* Validate the attributes of a CloudEvent.
|
||||
*
|
||||
* @param event the event to convert
|
||||
* @return the visitable implementation
|
||||
* @param cloudEvent the CloudEvent to validate
|
||||
*/
|
||||
public static CloudEventReader toVisitable(CloudEvent event) {
|
||||
if (event instanceof CloudEventReader) {
|
||||
return (CloudEventReader) event;
|
||||
} else {
|
||||
return new CloudEventReaderAdapter(event);
|
||||
}
|
||||
}
|
||||
|
||||
void validate(CloudEvent cloudEvent);
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package io.cloudevents.core;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventData;
|
||||
import io.cloudevents.core.builder.CloudEventBuilder;
|
||||
import io.cloudevents.core.mock.MyCloudEventData;
|
||||
import io.cloudevents.rw.CloudEventDataMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class CloudEventUtilsTest {
|
||||
|
||||
@Test
|
||||
void mapDataWithNullData() {
|
||||
AtomicInteger i = new AtomicInteger();
|
||||
CloudEventDataMapper<CloudEventData> mapper = data -> {
|
||||
i.incrementAndGet();
|
||||
return data;
|
||||
};
|
||||
|
||||
CloudEvent cloudEvent = CloudEventBuilder.v1()
|
||||
.withId("aaa")
|
||||
.withSource(URI.create("localhost"))
|
||||
.withType("bbb")
|
||||
.build();
|
||||
|
||||
assertThat(CloudEventUtils.mapData(cloudEvent, mapper))
|
||||
.isNull();
|
||||
assertThat(i)
|
||||
.hasValue(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapData() {
|
||||
AtomicInteger i = new AtomicInteger();
|
||||
CloudEventDataMapper<CloudEventData> mapper = data -> {
|
||||
i.incrementAndGet();
|
||||
return data;
|
||||
};
|
||||
|
||||
MyCloudEventData data = new MyCloudEventData(10);
|
||||
|
||||
CloudEvent cloudEvent = CloudEventBuilder.v1()
|
||||
.withId("aaa")
|
||||
.withSource(URI.create("localhost"))
|
||||
.withType("bbb")
|
||||
.withData(data)
|
||||
.build();
|
||||
|
||||
assertThat(CloudEventUtils.mapData(cloudEvent, mapper))
|
||||
.isEqualTo(data);
|
||||
assertThat(i)
|
||||
.hasValue(1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.cloudevents.core.data;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class PojoCloudEventDataTest {
|
||||
|
||||
@Test
|
||||
void testWrapAndMemoization() {
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, i -> i.toString().getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(data.getValue())
|
||||
.isEqualTo(10);
|
||||
|
||||
byte[] firstConversion = data.toBytes();
|
||||
|
||||
assertThat(firstConversion)
|
||||
.isEqualTo("10".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
assertThat(data.toBytes())
|
||||
.isSameAs(firstConversion);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAlreadySerializedValue() {
|
||||
byte[] serialized = "10".getBytes(StandardCharsets.UTF_8);
|
||||
PojoCloudEventData<Integer> data = PojoCloudEventData.wrap(10, v -> serialized);
|
||||
|
||||
assertThat(data.getValue())
|
||||
.isEqualTo(10);
|
||||
|
||||
assertThat(data.toBytes())
|
||||
.isSameAs(serialized);
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ public class DatarefExtensionTest {
|
|||
.build();
|
||||
|
||||
assertThat(event.getExtension(DatarefExtension.DATAREF))
|
||||
.isEqualTo(URI.create("http://example"));
|
||||
.isEqualTo("http://example");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
package io.cloudevents.core.impl;
|
||||
|
||||
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 {
|
||||
|
||||
@Test
|
||||
public void copyAndRemoveExtension() {
|
||||
assertThat(Data.V1_WITH_JSON_DATA_WITH_EXT.getExtensionNames())
|
||||
.contains("astring");
|
||||
|
||||
CloudEvent event = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withoutExtension("astring")
|
||||
.build();
|
||||
|
||||
assertThat(event.getExtensionNames())
|
||||
.doesNotContain("astring");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void copyAndRemoveMaterializedExtension() {
|
||||
DistributedTracingExtension ext = new DistributedTracingExtension();
|
||||
ext.setTraceparent("aaa"); // Set only traceparent
|
||||
|
||||
CloudEvent given = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension(ext)
|
||||
.build();
|
||||
assertThat(given.getExtensionNames())
|
||||
.contains("traceparent")
|
||||
.doesNotContain("tracestate");
|
||||
|
||||
CloudEvent have = CloudEventBuilder.v1(given)
|
||||
.withoutExtension(ext)
|
||||
.build();
|
||||
|
||||
assertThat(have.getExtensionNames())
|
||||
.doesNotContain("traceparent", "tracestate");
|
||||
assertThat(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.isEqualTo(have);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLongExtensionNameV1() {
|
||||
assertDoesNotThrow(() -> {
|
||||
CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("thisextensionnameistoolong", "")
|
||||
.build();
|
||||
});
|
||||
}
|
||||
|
||||
@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, () -> {
|
||||
CloudEvent cloudEvent = CloudEventBuilder.v1(Data.V1_WITH_JSON_DATA_WITH_EXT)
|
||||
.withExtension("ExtensionName", "")
|
||||
.build();
|
||||
});
|
||||
String expectedMessage = "Invalid extensions name: ExtensionName";
|
||||
String actualMessage = exception.getMessage();
|
||||
|
||||
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()
|
||||
);
|
||||
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue