Compare commits

...

206 Commits

Author SHA1 Message Date
Pierangelo Di Pilato 0d96691f6d
Migrate from OSSRH to Central Portal (#698)
Ref: https://central.sonatype.org/publish/publish-portal-maven/

Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2025-06-26 09:44:36 +02:00
Sleiman Jneidi c8a22b5175
build: upgrage to protobuf sdk 4.31.1 (#695)
Signed-off-by: Sleiman <sleiman.jneidi@apple.com>
Co-authored-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2025-06-20 14:56:34 +02:00
Sleiman Jneidi 57f9867a5c
build: remove maven cache (#696)
Signed-off-by: Sleiman <sleiman.jneidi@apple.com>
2025-06-20 09:43:41 +02:00
dependabot[bot] 43f0d5b138
Bump nokogiri from 1.16.5 to 1.18.3 in /docs (#685)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.5 to 1.18.3.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/v1.18.3/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.5...v1.18.3)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-12 08:18:56 +01:00
dependabot[bot] efe7e01b75
Bump org.apache.maven.plugins:maven-antrun-plugin from 3.0.0 to 3.1.0 (#682)
Bumps [org.apache.maven.plugins:maven-antrun-plugin](https://github.com/apache/maven-antrun-plugin) from 3.0.0 to 3.1.0.
- [Commits](https://github.com/apache/maven-antrun-plugin/compare/maven-antrun-plugin-3.0.0...maven-antrun-plugin-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-antrun-plugin
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-01-27 10:03:16 +01:00
dependabot[bot] 188c3c7085
Bump org.apache.qpid:proton-j from 0.33.7 to 0.34.1 (#678)
Bumps org.apache.qpid:proton-j from 0.33.7 to 0.34.1.

---
updated-dependencies:
- dependency-name: org.apache.qpid:proton-j
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 13:13:12 +01:00
dependabot[bot] 659bb8cc01
Bump org.apache.kafka:kafka-clients from 3.0.0 to 3.7.1 in /kafka (#681)
Bumps org.apache.kafka:kafka-clients from 3.0.0 to 3.7.1.

---
updated-dependencies:
- dependency-name: org.apache.kafka:kafka-clients
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-20 13:12:46 +01:00
Kristiyan Marinov 9981635e7d
fix: disallow empty 'subject' context attribute (#679)
Specification clearly states that 'subject' is optional but if present, MUST be non-empty
(spec at https://github.com/cloudevents/spec/blob/v1.0/spec.md#subject)

Signed-off-by: Kristiyan Marinov <kristiyanm@gmail.com>
2024-11-04 09:53:36 +01:00
dependabot[bot] b54df5ca0c
Bump io.projectreactor:reactor-core from 3.5.1 to 3.6.11 (#677)
Bumps [io.projectreactor:reactor-core](https://github.com/reactor/reactor-core) from 3.5.1 to 3.6.11.
- [Release notes](https://github.com/reactor/reactor-core/releases)
- [Commits](https://github.com/reactor/reactor-core/compare/v3.5.1...v3.6.11)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-30 06:47:37 +01:00
dependabot[bot] a3decf768c
Bump org.slf4j:slf4j-simple from 1.7.36 to 2.0.16 (#674)
Bumps org.slf4j:slf4j-simple from 1.7.36 to 2.0.16.

---
updated-dependencies:
- dependency-name: org.slf4j:slf4j-simple
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 08:01:59 +01:00
dependabot[bot] 215e67f2c3
Bump rexml from 3.3.6 to 3.3.9 in /docs (#675)
Bumps [rexml](https://github.com/ruby/rexml) from 3.3.6 to 3.3.9.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.3.6...v3.3.9)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-29 08:01:19 +01:00
dependabot[bot] 514e00d75b
Bump rexml from 3.2.8 to 3.3.6 in /docs (#665)
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.8 to 3.3.6.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.8...v3.3.6)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 14:24:28 +02:00
dependabot[bot] f7ac215bf1
Bump org.eclipse.jetty:jetty-server in /examples/basic-http (#672)
Bumps org.eclipse.jetty:jetty-server from 9.4.51.v20230217 to 9.4.55.v20240627.

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-22 14:23:36 +02:00
dependabot[bot] 5f4abd144a
Bump org.apache.avro:avro from 1.11.3 to 1.11.4 in /formats/avro-compact (#671)
Bumps org.apache.avro:avro from 1.11.3 to 1.11.4.

---
updated-dependencies:
- dependency-name: org.apache.avro:avro
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-08 17:25:38 +02:00
github-actions[bot] 8d59d29bb1
Bump to 4.0.1 (#656)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierDipi@users.noreply.github.com>
2024-10-02 19:49:23 +02:00
Rohan Mallya 6ab42de007
docs: correcting import to EventFormatProvider (#658)
Signed-off-by: Rohan R Mallya <bangalorerohan@gmail.com>
2024-10-02 19:48:40 +02:00
dependabot[bot] 2e87988d0c
Bump org.apache.maven.plugins:maven-install-plugin from 2.5.1 to 3.1.3 (#664)
Bumps [org.apache.maven.plugins:maven-install-plugin](https://github.com/apache/maven-install-plugin) from 2.5.1 to 3.1.3.
- [Release notes](https://github.com/apache/maven-install-plugin/releases)
- [Commits](https://github.com/apache/maven-install-plugin/compare/maven-install-plugin-2.5.1...maven-install-plugin-3.1.3)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-install-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-02 19:48:09 +02:00
David Simansky 6d958b69d4
Fix cloudevents-sql artifactId in bom (#669)
Signed-off-by: David Simansky <dsimansk@redhat.com>
2024-10-02 19:47:43 +02:00
dependabot[bot] 1de03ad38a
Bump org.apache.rocketmq:rocketmq-client-java from 5.0.4 to 5.0.7 (#653)
Bumps [org.apache.rocketmq:rocketmq-client-java](https://github.com/apache/rocketmq-clients) from 5.0.4 to 5.0.7.
- [Release notes](https://github.com/apache/rocketmq-clients/releases)
- [Commits](https://github.com/apache/rocketmq-clients/compare/java-5.0.4...java-5.0.7)

---
updated-dependencies:
- dependency-name: org.apache.rocketmq:rocketmq-client-java
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-03 14:48:32 +02:00
Pierangelo Di Pilato 4c5d0efb89
Make CloudEventValidatorProvider thread safe (#650)
Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2024-07-02 17:39:05 +02:00
github-actions[bot] 01a9111d8b
Bump to 4.0.0 in docs (#649)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierDipi@users.noreply.github.com>
2024-07-01 20:29:40 +02:00
Pierangelo Di Pilato e056d1b2d8
Allow bumping versions in `/docs` only (#648)
This will allow changing the version in the documentation website
when a new release is published.

Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2024-07-01 17:24:48 +02:00
github-actions[bot] efdf0ba866
Bump to 4.1.0-SNAPSHOT (#645)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierDipi@users.noreply.github.com>
2024-07-01 12:10:47 +02:00
github-actions[bot] bd90d903ce
Bump to 4.0.0-SNAPSHOT (#644)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierDipi@users.noreply.github.com>
2024-07-01 11:41:33 +02:00
dependabot[bot] 52a98d778c
Bump rexml from 3.2.5 to 3.2.8 in /docs (#635)
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.5 to 3.2.8.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.5...v3.2.8)

---
updated-dependencies:
- dependency-name: rexml
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-01 10:59:57 +02:00
Calum Murray 010627e784
CE SQL v1 (#641)
- add exception factory for cesql exceptions
- extend EvaluationResult to be usable internally
- expressions use results instead of a thrower interface
- functions use results instead of a thrower interface
- parser handles not equals correctly, does not eagerly evaluate when there may be an error
- parser handles integer literals properly
- updated test files to test v1 spec

Signed-off-by: Calum Murray <cmurray@redhat.com>
Co-authored-by: Pierangelo Di Pilato <pierangelodipilato@gmail.com>
2024-06-21 17:28:26 +02:00
Juan Martinez a7904823c3
fix: invalid automatic module name (#639)
Inspired by [Automatic-Module-Name: Calling All Java Library Maintainers](https://dzone.com/articles/automatic-module-name-calling-all-java-library-maintainers), I added the module names for the non-specified modules on their respective `pom.xml` file

Signed-off-by: Juan MARTINEZ <Jummartinezro@users.noreply.github.com>
2024-06-21 09:24:33 +02:00
Mickaël Schoentgen e5a35ac472
doc: update & uniformize package version (#640)
Signed-off-by: Mickaël Schoentgen <Mickael.Schoentgen@hyland.com>
2024-06-20 12:08:07 +02:00
dependabot[bot] 4d204cef89
Bump org.xmlunit:xmlunit-core from 2.9.0 to 2.10.0 in /formats/xml (#631)
Bumps [org.xmlunit:xmlunit-core](https://github.com/xmlunit/xmlunit) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/xmlunit/xmlunit/releases)
- [Changelog](https://github.com/xmlunit/xmlunit/blob/main/RELEASE_NOTES.md)
- [Commits](https://github.com/xmlunit/xmlunit/compare/v2.9.0...v2.10.0)

---
updated-dependencies:
- dependency-name: org.xmlunit:xmlunit-core
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 11:04:06 +02:00
dependabot[bot] 43afb6ceaa
Bump nokogiri from 1.16.3 to 1.16.5 in /docs (#632)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.16.3 to 1.16.5.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.16.3...v1.16.5)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-14 11:03:01 +02:00
dependabot[bot] 9c6e7fd11e
Bump io.vertx:vertx-core from 4.3.7 to 4.5.3 in /http/vertx (#622)
Bumps [io.vertx:vertx-core](https://github.com/eclipse/vert.x) from 4.3.7 to 4.5.3.
- [Commits](https://github.com/eclipse/vert.x/compare/4.3.7...4.5.3)

---
updated-dependencies:
- dependency-name: io.vertx:vertx-core
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-09 10:13:49 +02:00
github-actions[bot] 98deac1599
Bump to 3.1.0-SNAPSHOT (#613)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierDipi@users.noreply.github.com>
2024-03-26 09:59:22 +01:00
Pierangelo Di Pilato 05baf9be8a
Run workflows on any major version branch (#615)
Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2024-03-26 08:42:45 +01:00
Pierangelo Di Pilato 3add823e00
Run workflows on any major version branch (#614)
Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2024-03-26 08:39:35 +01:00
dependabot[bot] 5a03173dde
Bump org.apache.maven.plugins:maven-source-plugin from 2.2.1 to 3.3.0 (#608)
Bumps [org.apache.maven.plugins:maven-source-plugin](https://github.com/apache/maven-source-plugin) from 2.2.1 to 3.3.0.
- [Commits](https://github.com/apache/maven-source-plugin/compare/maven-source-plugin-2.2.1...maven-source-plugin-3.3.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-source-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-26 08:12:40 +01:00
dependabot[bot] 9ee16fb48c
Bump nokogiri from 1.13.10 to 1.16.3 in /docs (#609)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.10 to 1.16.3.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.10...v1.16.3)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-26 08:11:52 +01:00
dependabot[bot] 0db4446163
Bump com.github.hanleyt:jersey-junit from 2.1.0 to 2.2.0 (#584)
Bumps [com.github.hanleyt:jersey-junit](https://github.com/hanleyt/jersey-junit) from 2.1.0 to 2.2.0.
- [Release notes](https://github.com/hanleyt/jersey-junit/releases)
- [Commits](https://github.com/hanleyt/jersey-junit/compare/2.1.0...2.2.0)

---
updated-dependencies:
- dependency-name: com.github.hanleyt:jersey-junit
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2024-03-11 15:57:25 +01:00
touchkey 111fb55cfd
feat: Customizing Cloudevents validation (#594)
Add SPI for custom CloudEvent validation.

Signed-off-by: vbhat6 <vinayas_bhat@intuit.com>
2024-02-15 07:48:55 +01:00
Matej Vasek 55fddb35fc
feat: JSON format to assume JSON content-type where possible (#604)
Signed-off-by: Matej Vašek <mvasek@redhat.com>
2024-02-08 09:49:19 +01:00
Calum Murray 7b9d020acc
Update cesql TCK tests (#603)
Signed-off-by: Calum Murray <cmurray@redhat.com>
2024-01-31 11:51:03 +01:00
Doug Davis fb11b94f2b
Add link to CloudEvent security mailing list (#599)
Signed-off-by: Doug Davis <dug@microsoft.com>
2023-10-16 15:26:53 +02:00
Boris Stumm eaef3becdd
#588 Update jackson to 2.15.2 (#589)
Signed-off-by: Boris Stumm <bs@boris-stumm.de>
2023-10-02 10:08:48 +02:00
dependabot[bot] b30324e916
Bump org.apache.avro:avro from 1.11.2 to 1.11.3 in /formats/avro-compact (#593)
Bumps org.apache.avro:avro from 1.11.2 to 1.11.3.

---
updated-dependencies:
- dependency-name: org.apache.avro:avro
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-10-02 10:08:22 +02:00
Doug Davis 1f9fa13231
Align SDK governance docs (#590)
Related to https://github.com/cloudevents/spec/pull/1226

Signed-off-by: Doug Davis <dug@microsoft.com>
2023-09-28 09:29:59 +02:00
dependabot[bot] a135755ec6
Bump jackson-dataformat-yaml from 2.11.2 to 2.15.2 (#577)
Bumps [jackson-dataformat-yaml](https://github.com/FasterXML/jackson-dataformats-text) from 2.11.2 to 2.15.2.
- [Commits](https://github.com/FasterXML/jackson-dataformats-text/compare/jackson-dataformats-text-2.11.2...jackson-dataformats-text-2.15.2)

---
updated-dependencies:
- dependency-name: com.fasterxml.jackson.dataformat:jackson-dataformat-yaml
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 09:19:35 +02:00
dependabot[bot] 677a2c2628
Bump jetty-server in /examples/basic-http (#579)
Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.41.v20210516 to 9.4.51.v20230217.
- [Release notes](https://github.com/eclipse/jetty.project/releases)
- [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.41.v20210516...jetty-9.4.51.v20230217)

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 09:19:06 +02:00
dependabot[bot] 826e099fc0
Bump io.openliberty.arquillian:arquillian-liberty-managed-jakarta-junit (#582)
Bumps [io.openliberty.arquillian:arquillian-liberty-managed-jakarta-junit](https://github.com/OpenLiberty/arquillian-liberty-dependencies) from 2.1.0 to 2.1.4.
- [Release notes](https://github.com/OpenLiberty/arquillian-liberty-dependencies/releases)
- [Commits](https://github.com/OpenLiberty/arquillian-liberty-dependencies/compare/arquillian-liberty-jakarta-dependencies-2.1.0...arquillian-liberty-jakarta-dependencies-2.1.4)

---
updated-dependencies:
- dependency-name: io.openliberty.arquillian:arquillian-liberty-managed-jakarta-junit
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 09:18:34 +02:00
dependabot[bot] 76366338fc
Bump truth-proto-extension from 1.1 to 1.1.5 (#580)
Bumps truth-proto-extension from 1.1 to 1.1.5.

---
updated-dependencies:
- dependency-name: com.google.truth.extensions:truth-proto-extension
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-20 18:03:41 +02:00
Alex Collins 4ef304115a
[working-draft] Support Avro Compact Format (#578)
Add support for working-draft spec Avro compact format: 777d0c0398

Signed-off-by: Alex Collins <alex_collins@intuit.com>
2023-07-20 18:02:59 +02:00
dependabot[bot] 582feed520
Bump maven-gpg-plugin from 1.6 to 3.1.0 (#564)
Bumps [maven-gpg-plugin](https://github.com/apache/maven-gpg-plugin) from 1.6 to 3.1.0.
- [Commits](https://github.com/apache/maven-gpg-plugin/compare/maven-gpg-plugin-1.6...maven-gpg-plugin-3.1.0)

---
updated-dependencies:
- dependency-name: org.apache.maven.plugins:maven-gpg-plugin
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-23 12:08:54 +02:00
github-actions[bot] 5ef1088a19
Bump to 3.0.0-SNAPSHOT (#571)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierDipi@users.noreply.github.com>
2023-05-23 10:55:49 +02:00
Aaron Ai 698cdf7ad4
feat: add rocketmq binding (#554)
Spec details: a6978cf562/rocketmq-cloudevents-binding/rocketmq-transport-binding.md

Signed-off-by: Aaron Ai <yangkun.ayk@alibaba-inc.com>
2023-05-23 10:49:08 +02:00
skepticoitusInteruptus 4ebeab0e0f
Refactor to Facilitate Decoupling from Concrete Implementations of EventFormat (#539)
- Introduce ContentType enum
- Resolve formats by using the ContentType enum

Signed-off-by: Randi Sheaffer-Klass <97033958+skepticoitusInteruptus@users.noreply.github.com>
2023-04-21 10:41:10 +02:00
Jem Day 4c81f3eacc
Make ProtoCloudEventData consistent (#535)
Modified ProtoCloudEventData to always return a 
Protobuf Any object - this ensures it is coherent with
the Protobuf CloudEvent format specification.

It remains possible to wrap any Protobuf 'Message'
object directly (which includes an 'Any') as a
convienience to reduce application code.

Signed-off-by: Jem Day <Jem.Day@cliffhanger.com>
2023-03-10 09:35:24 +01:00
Pierangelo Di Pilato 569e025cf0
Fix javadoc build (#529) (#534)
Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2023-03-06 18:25:53 +01:00
Jem Day 3614a4f5f4
Fixed Protobuf data corruption for CloudEvent serialized/deserialized several times (#524)
Fixed issue where mutiple serialize/de-serialize operations
would result in corrupted data if the data was a protobuf
message object.

- Introduced equality checks for ProtoDataWrapper.
- Refactored and cleaned up data-wrappers.

Fixes https://github.com/cloudevents/sdk-java/issues/523

Signed-off-by: Jem Day <Jem.Day@cliffhanger.com>
2023-02-27 11:48:07 +01:00
mxsm d64aff7327
[#521] Remove unused imports (#522)
Signed-off-by: mxsm <ljbmxsm@gmail.com>
2023-02-20 18:06:28 +01:00
dependabot[bot] d1cff75230
Bump activesupport from 6.0.6 to 6.0.6.1 in /docs (#517)
Bumps [activesupport](https://github.com/rails/rails) from 6.0.6 to 6.0.6.1.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.0.4.2/activesupport/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v6.0.6...v6.0.6.1)

---
updated-dependencies:
- dependency-name: activesupport
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-20 08:42:50 +01:00
Ruben Romero Montes 1591cb337a
[#465] Upgrade Quarkus examples with binary and structured events (#515)
Signed-off-by: ruromero <rromerom@redhat.com>
2023-01-24 16:54:22 +01:00
Pierangelo Di Pilato f71303b7b7
Switch default branch to main (#506)
Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2023-01-12 19:03:51 +01:00
Pierangelo Di Pilato d59b33307a
Run tests before deploy on Java 17 (#504)
Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2023-01-10 16:49:47 +01:00
dependabot[bot] e0d1961f35
Bump nokogiri from 1.13.9 to 1.13.10 in /docs (#496)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.9 to 1.13.10.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.9...v1.13.10)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 16:41:29 +01:00
dependabot[bot] 7c6b52ab30
Bump reactor-core from 3.4.21 to 3.5.1 (#499)
Bumps [reactor-core](https://github.com/reactor/reactor-core) from 3.4.21 to 3.5.1.
- [Release notes](https://github.com/reactor/reactor-core/releases)
- [Commits](https://github.com/reactor/reactor-core/compare/v3.4.21...v3.5.1)

---
updated-dependencies:
- dependency-name: io.projectreactor:reactor-core
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-10 16:10:42 +01:00
Jem Day 433ec5b274
Initial Implementation of XML Format (#448)
Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>
Signed-off-by: Jem Day <Jem.Day@cliffhanger.com>
2023-01-10 09:48:36 +01:00
Doug Davis 40fe91a5e0
Use UTF-8 when using getBytes (#491)
Closes #488

Signed-off-by: Doug Davis <dug@microsoft.com>
2023-01-05 18:01:14 +01:00
Pierangelo Di Pilato a43f90f4e2
Create dependabot.yml 2023-01-02 15:19:06 +01:00
dependabot[bot] e488269510
Bump nokogiri from 1.13.6 to 1.13.9 in /docs (#486)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.6 to 1.13.9.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.6...v1.13.9)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-02 15:11:24 +01:00
Fabio José 7d50c7fc7a
Bump Vertx to 4.3.7 (#495)
The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-3167773

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2023-01-02 15:10:38 +01:00
Averi Kitsch f1a86af656
Fix curl command for Spring example (#478)
Signed-off-by: Averi Kitsch <akitsch@google.com>
2023-01-02 13:36:30 +01:00
Doug Davis 354f7a16ef
Upgrade kafka, protobuf, and vert.x versions (#492)
Closes #489

Signed-off-by: Doug Davis <dug@microsoft.com>
2022-12-09 12:35:19 +01:00
Pierangelo Di Pilato 9132a13d81
Remove deprecated constructor usage for JsonCloudEventData (#483)
Fixes #482

Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2022-10-12 08:58:41 +02:00
dependabot[bot] a491c85eb2
Bump tzinfo from 1.2.8 to 1.2.10 in /docs (#467)
Bumps [tzinfo](https://github.com/tzinfo/tzinfo) from 1.2.8 to 1.2.10.
- [Release notes](https://github.com/tzinfo/tzinfo/releases)
- [Changelog](https://github.com/tzinfo/tzinfo/blob/master/CHANGES.md)
- [Commits](https://github.com/tzinfo/tzinfo/compare/v1.2.8...v1.2.10)

---
updated-dependencies:
- dependency-name: tzinfo
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-12 08:56:14 +02:00
Frédéric Déléchamp 6362bfbcd6
Strip parameters from data content types to assess if it's JSON format (#484)
Signed-off-by: Frederic Delechamp <fdelechamp@guidewire.com>
2022-10-06 10:42:37 +02:00
Alex Butcher f08a099ed9
Restful-ws jakarta ee9 namespace support (#469)
* Replace javax restful-ws namespace usage with jakarata in own module
* Add microprofile with openliberty example using new jar
* Update gitignore to cover all copied directories
* Update Jersey Version to support jakarta.* namespace packages
* Port jersey integration tests from javax.* to jakarta.*
* Undo changes to existing integration test package names
* Add Integration test for Microprofile/Liberty for Jee9 package
* Add documentation for new Jakarta package

Signed-off-by: alex-butcher <21243172+abutch3r@users.noreply.github.com>
2022-09-27 09:16:03 +02:00
Vikram Vuppla 4139fb7e57
Fix typo in word Covert (#480)
Signed-off-by: Vikram Vuppla <naga.vicky@gmail.com>
2022-09-19 17:31:31 +02:00
Gerard Klijs adde53c817
Correct URL to naming-conventions (#477)
Signed-off-by: Gerard Klijs <gerard.klijs@axoniq.io>
2022-09-14 15:46:09 +02:00
Jorge Oliva 0dc10251ff
Fix example in "Materialize an Extension" (#475)
Was using `ExtensionParser` instead `ExtensionProvider`

Signed-off-by: Jorge Oliva <Jorge.OlivaFernandez@santander.co.uk>
2022-09-14 11:39:25 +02:00
github-actions[bot] b9eaa2fcaa
Bump to 2.5.0-SNAPSHOT (#474)
Signed-off-by: GitHub <noreply@github.com>
Co-authored-by: pierDipi <pierdipi@redhat.com>
2022-09-06 15:52:34 +02:00
Isaac Aymerich f8d27b08bf
Support dynamic JSON data content type (#471)
Now checks if `datacontenttype` matches the regex:

`^(application|text)\/([a-zA-Z]+\+)?json$")`

This regex support 
`application/foobar+json`
or standard
```
application/json
text/json
```

Signed-off-by: Isaac Aymerich <isaac.aymerich@roche.com>
2022-09-05 13:14:38 +02:00
Matej Vasek 9125136530
chore: update Quarkus example (#466)
* Server accepts both: binary and structured encoded events.
* Emitter contains comment describing how to switch between binary and
  structured encoding for emitted events.

Signed-off-by: Matej Vasek <mvasek@redhat.com>
2022-07-19 12:27:44 +02:00
Gustavo Tedesco f35e6e610a
CVE-2020-36518 - bump jackson from 2.11.2 to 2.13.3 (#464)
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-36518

Signed-off-by: Gustavo Tedesco <gustavo.tedesco@unico.io>
2022-07-13 17:16:38 +02:00
Pierangelo Di Pilato 45ec85f8c1
Optimize `isCloudEventsHeader` check (#445)
Instead of using:
```java
key.substring(0, CE_PREFIX.length()).toLowerCase().startsWith(CE_PREFIX);
```
we can just use:
```java
key.regionMatches(true /* ignoreCase */, 0, CE_PREFIX, 0, CE_PREFIX.length());
```

Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2022-07-13 10:18:17 +02:00
dependabot[bot] 1d87fb7191
Bump nokogiri from 1.13.3 to 1.13.6 in /docs (#457)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.13.3 to 1.13.6.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.13.3...v1.13.6)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-25 16:06:16 +02:00
Snyk bot 8f9b741306
[Snyk] Security upgrade io.vertx:vertx-core from 4.0.0 to 4.2.5 (#455)
* fix: http/vertx/pom.xml to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1020439
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1070799
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1082234
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1082235
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1082236
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1083991
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1089809
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1317097
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1584063
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-1584064
- https://snyk.io/vuln/SNYK-JAVA-IONETTY-2314893

* Bump Vert.x to 4.2.5

Co-authored-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2022-04-08 11:13:07 +02:00
dependabot[bot] 2dd8ba95dd
Bump nokogiri from 1.12.5 to 1.13.3 in /docs (#447)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.12.5 to 1.13.3.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.12.5...v1.13.3)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-01 19:01:24 +02:00
Pierangelo Di Pilato 1c29726e8c
Add `CONTRIBUTING.md` and `MAINTAINERS.md` files (#454)
These 2 new files are based on the existing `pr_guidelines.md`
and `maintainers_guide.md`

Signed-off-by: Pierangelo Di Pilato <pierdipi@redhat.com>
2022-04-01 19:00:37 +02:00
mEstrazulas 6681205733
Fix Spring structured example (#451)
Signed-off-by: Micael Vianna <estrazulas@gmail.com>
Signed-off-by: Micael Estrazulas Vianna <m.vianna@kigroup.de>
2022-03-18 23:24:37 +01:00
Jem Day a4bc7a8368
Protobuf: Enhance textual content-types detection (#444)
- Move content-type introspection in a separate support class.
- Add unit tests to ensure introspection is correct.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>
2022-01-31 10:18:22 +01:00
github-actions[bot] 4784f03e8c
Bump to 2.4.0-SNAPSHOT (#439)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: pierDipi <pierdipi@redhat.com>
2021-12-21 12:18:07 +01:00
Pierangelo Di Pilato ace6859ae0
Bump version to 2.3.0 (#437)
Signed-off-by: Pierangelo Di Pilato <pdipilat@redhat.com>
2021-12-21 12:06:57 +01:00
Pierangelo Di Pilato cc786251d5
Handle NullNode for optional attributes in Jackson CloudEventDeserializer (#432)
In `getOptionalStringNode` we should handle `JsonNode`s that are
instances of `NullNode`.

Signed-off-by: Pierangelo Di Pilato <pdipilat@redhat.com>
2021-12-21 11:22:02 +01:00
Myeonghyeon-Lee ceb06757a3
Ignore invalid extension names in jackson CloudEventDeserializer (#429)
Signed-off-by: mhyeon-lee <mhyeon.lee@navercorp.com>
2021-12-10 10:26:47 +01:00
Pierangelo Di Pilato 8d91cdaee6
Run tests with Java 17 (#426)
Signed-off-by: Pierangelo Di Pilato <pdipilat@redhat.com>
2021-12-01 09:05:02 +01:00
Pierangelo Di Pilato d00ad967c0
Fix the Java Doc build (#424)
- https://vertx.io/docs/apidocs/ returns 404, so removing it
- Fix Java Doc error for missing `@param`

Signed-off-by: Pierangelo Di Pilato <pdipilat@redhat.com>
2021-11-17 16:55:27 +01:00
Dave Syer 9231e6d230
Add example of Spring WebClient usage (#418)
- Add example of WebClient usage
- Add additional test case with WebClient usage

Signed-off-by: Dave Syer <dsyer@vmware.com>
2021-11-17 16:25:59 +01:00
Joke de Buhr a94bc5c81c
Close input stream on CloudEventHttpMessageReader (#421)
Signed-off-by: Joke de Buhr <joke.debuhr@eventim.de>
2021-11-03 09:54:38 +01:00
Dmitrii Bocharov 32adfe9123
Fix NPE in CloudEventDeserializer when deserializing header with null value (#415)
Signed-off-by: Dmitrii Bocharov <dmitrii.bocharov@embedit.cz>
2021-11-03 08:28:54 +01:00
dependabot[bot] 0277ee4ae4
Bump spring-framework-bom in /examples/restful-ws-spring-boot (#423)
Bumps [spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.2.8.RELEASE to 5.2.9.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.2.8.RELEASE...v5.2.9.RELEASE)

---
updated-dependencies:
- dependency-name: org.springframework:spring-framework-bom
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-03 08:26:35 +01:00
dependabot[bot] 202849307c
Bump addressable from 2.7.0 to 2.8.0 in /docs (#406)
Bumps [addressable](https://github.com/sporkmonger/addressable) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/sporkmonger/addressable/releases)
- [Changelog](https://github.com/sporkmonger/addressable/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sporkmonger/addressable/compare/addressable-2.7.0...addressable-2.8.0)

---
updated-dependencies:
- dependency-name: addressable
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-03 08:07:20 +01:00
dependabot[bot] 624ac693d8
Bump nokogiri from 1.11.5 to 1.12.5 in /docs (#419)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.11.5 to 1.12.5.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.11.5...v1.12.5)

---
updated-dependencies:
- dependency-name: nokogiri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-03 08:06:43 +01:00
dependabot[bot] 5a323942d3
Bump jetty-server in /examples/basic-http (#403)
Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.38.v20210224 to 9.4.41.v20210516.
- [Release notes](https://github.com/eclipse/jetty.project/releases)
- [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.38.v20210224...jetty-9.4.41.v20210516)

---
updated-dependencies:
- dependency-name: org.eclipse.jetty:jetty-server
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-05 12:22:52 +02:00
dependabot[bot] 1587708805
Bump spring-framework-bom (#382)
Bumps [spring-framework-bom](https://github.com/spring-projects/spring-framework) from 5.2.8.RELEASE to 5.2.9.RELEASE.
- [Release notes](https://github.com/spring-projects/spring-framework/releases)
- [Commits](https://github.com/spring-projects/spring-framework/compare/v5.2.8.RELEASE...v5.2.9.RELEASE)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-05 12:22:46 +02:00
Francesco Guardiani 06c4ec5385
Moved exception factory methods internally (#402)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-07-05 11:43:46 +02:00
Nicolas Vervelle 73a3c370d5
build: provide a Bill of Materials artifact for easier integration in projects (#405)
Signed-off-by: Nicolas Vervelle <nicolas.vervelle@quicksign.com>
2021-06-28 09:30:41 +02:00
github-actions[bot] 722f5205b3
Bump to 2.3.0-SNAPSHOT (#400)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-06-17 12:18:55 +02:00
github-actions[bot] 8ad857d8c7
Bump to 2.2.0 (#399)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-06-17 12:05:04 +02:00
Francesco Guardiani baa9b5927a
[CESQL] Reorganize package implementation classes (#397)
* Move classes around to improve package organization

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed bad imports

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-06-15 10:31:14 +02:00
Francesco Guardiani c41a2c3ba7
Cleanup CloudEventUtils (#398)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-06-14 16:30:07 +02:00
Francesco Guardiani 3651cdae18
Add a method to simplify casting (#396)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-06-07 08:36:02 +02:00
dependabot[bot] ee4c85b1a1
Bump nokogiri from 1.11.1 to 1.11.5 in /docs (#393)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.11.1 to 1.11.5.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.11.1...v1.11.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-04 09:41:26 +02:00
Francesco Guardiani a0b0835180
[CESQL] Constant folding (#392)
* Added visitor for the expressions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* First constant folding draft

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Constant folding for unary expressions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More testing
Constant folding for exists expression

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Little mistake

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added a ParserBuilder

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-06-04 09:19:38 +02:00
Pei-Tang Huang 687e03bac5
Update the link to http4k Cloud Events module. (#394)
Signed-off-by: Pei-Tang Huang <tangtheone@gmail.com>
2021-05-26 09:36:30 +02:00
Francesco Guardiani 208b18c299
Build release branches too (#386)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-05-06 14:07:52 +02:00
Francesco Guardiani 9d45943844
Fix like expression (#381)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-05-03 10:05:26 +02:00
shnplr 0a1a03db64
fix: Update encoding for structured event (#384)
Signed-off-by: Paul Strachan <paul.strachan@det.nsw.edu.au>
2021-05-03 09:39:48 +02:00
dependabot[bot] 7f355d10c1
Bump rexml from 3.2.4 to 3.2.5 in /docs (#383)
Bumps [rexml](https://github.com/ruby/rexml) from 3.2.4 to 3.2.5.
- [Release notes](https://github.com/ruby/rexml/releases)
- [Changelog](https://github.com/ruby/rexml/blob/master/NEWS.md)
- [Commits](https://github.com/ruby/rexml/compare/v3.2.4...v3.2.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-03 09:34:47 +02:00
Francesco Guardiani 3234e30e55
CESQL Benchmark (#380)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-04-30 11:01:02 +02:00
github-actions[bot] c8f10e9215
Bump to 2.2.0-SNAPSHOT (#378)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-04-28 15:50:41 +02:00
github-actions[bot] ba9ccad5d2
Bump to 2.1.0 (#377)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-04-28 15:24:41 +02:00
Francesco Guardiani 78355bb225
Fix #326 (#376)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-04-28 15:15:45 +02:00
Francesco Guardiani 2730ae4a13
Expression language (#363)
* Configured the sql package

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Bootstrap implementation

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Literal done

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More progress

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Progress

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Sync contract

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fix type cohercion for event type system

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* In expression + sync grammar

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Implemented binary expressions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Implemented Like expression

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Big refactor

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More testing
Fix math

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Implemented all the functions!

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Refactored logical expressions implementation
More testing

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More coverage and tests

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fixed ConcatFunction and added ConcatWSFunction

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fixed IN type casting

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added ABS function

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fix SUBSTRING implementation

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More nits

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* WIP Javadoc-ing

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fix division by 0

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Bootstrapped TCK

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added comparison operators to tck

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added logical operators, case sensitivity and casting functions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Copied all the tests to the tck

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed Java tests now covered by the TCK

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added integer builtin test case

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added fail fast evaluation mode

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More changes
More Javadoc

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Typo

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fix bad javadoc

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Another CONCAT_WS test case

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Import yaml just for testing

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2021-04-28 14:58:55 +02:00
Johan Haleby 30fd6769eb
fix: Adding withoutData, withoutDataContentType and withoutDataSchema to CloudEventBuilder (#374)
Signed-off-by: Johan Haleby <johan.haleby@gmail.com>
2021-04-23 13:59:39 +02:00
dependabot[bot] ff07dd8315
Bump jetty-server in /examples/basic-http (#372)
Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.35.v20201120 to 9.4.38.v20210224.
- [Release notes](https://github.com/eclipse/jetty.project/releases)
- [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.35.v20201120...jetty-9.4.38.v20210224)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-19 12:38:51 +02:00
Erik Paulson 928ebcfd6f
formats: Add support for protobuf format (#348)
* formats: Add support for protobuf format

Adds support for the Protocol Buffer CloudEvent format defined at
https://github.com/cloudevents/spec/blob/v1.0.1/protobuf-format.md.

Compiles the Proto3 file taken from the spec repo into generated Java
protobuf classes. These classes are used to convert the SDK
representation of a CloudEvent to and from the protobuf format.

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* Address feedback in PR #348

- Adds service file for event format autoloading
- Addresses some field access issues
- Treats unset fields as omitted
- Updates and adds documentation

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* Add missing attribute writer methods for CloudEventBuilders

Without these methods, binary attributes are interpreted as
Strings instead of byte[].

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* Added test data files.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* - Now executes tests related to wire-format files.
- Supports V03 dialect

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* - Added new ProtoCloudEventData construct to support proto message based data.
- Added some more test files.

- When the PR related to binary context attributes is merged we can extend the
test use-cases appropriately.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* - Merged changes related to binary context attributes.
- Modified proto format to process binary context attributes
- Added CloudEventData varient to hold proto messages (requires tests)
- Will add further tests once the failing test is addressed

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Added test for protobuf data

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Indicate that v0.3 events are supported by Protobuf Format

Even though the protobuf spec came out for v1, the attributes are
fairly easily mapped back to v0.3.

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* Add missing comments; fix formatting; minor refactoring

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* Create a full Protobuf CloudEventWriter

Converts the ProtoContextWriter class to a ProtoCloudEventWriter class
and modifies the format code to use it instead of manually writing
data to the output.

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* - Addressed Review Comments.
- Introduced a default ProtoDataWrapper
- Tests updated.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Address Review Comments

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Test cleanup and timezone testing

This does some tweaking to tests by moving to using assertj and fixing
whitespace. It also adds a new test to ensure that timezones are handled
correctly.

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

* Formatting cleanup

- Remove unecessary whitespace
- Fix Javadocs
- Delete unused code

Signed-off-by: Erik Paulson <epaulson@apexclearing.com>

Co-authored-by: Day, Jeremy(jday) <jday@paypal.com>
2021-04-19 09:24:26 +02:00
Pierangelo Di Pilato a4613c00d2
Avoid allocating an array on extension validation (#367)
Signed-off-by: Pierangelo Di Pilato <pierangelodipilato@gmail.com>
2021-04-06 10:14:08 +02:00
Pierangelo Di Pilato 69f0e20549
CloudEvents attribute names SHOULD NOT exceed 20 chars (#366)
As per spec [1], attribute names SHOULD NOT and not MUST NOT
exceed 20 characters in length.

[1] https://github.com/cloudevents/spec/blob/master/spec.md#attribute-naming-conventionthis

Signed-off-by: Pierangelo Di Pilato <pierangelodipilato@gmail.com>
2021-04-06 10:00:55 +02:00
Jem Day e2b13109e4
Throw exceptions when atempting to handling unsupport CloudEvent formats. (#362)
- Modfy logic that selects between structured and binary modes during reception.
- Introduced new test scenarios to veify behavior.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>
2021-03-26 09:55:23 +01:00
Jem Day 5e3bfc890f
Specification Compliant handling of numeric context attributes (#358)
* - Added tests case to verify expected handling of numeric context attributes
- Updated serializer.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* - Added @deprecated marker for CloudEventContextWriter.set(name, Number)
- Added use of new method for JSON serializer.

Cleanup of deprecated implementations can occur independantly.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Addressed Review Comments

- Now throws exception when non specification compliant numeric
  attribute values are received during deserialization.

- Added test cases to verify deserialization exceptions.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Address Review Comments

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Address Review Comment

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>
2021-03-24 16:58:33 +01:00
Jem Day 13f8b56618
Introduced support for Binary attribute types. (#353)
* Introduced support for Binary attribute types.

Added test data example
Added unit-test for JSON Format

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* documentation tweak

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* - Addressed review comment.
- Removed the withContextAttribute(string, Integer).
   - This should be a seperate PR, was mixed-in by accident.

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>

* Address review comments

Signed-off-by: Day, Jeremy(jday) <jday@paypal.com>
2021-03-10 11:29:38 +01:00
Dave Syer a419d8bba3
Add Spring Cloud Function sample (#356)
* Add Spring Cloud Function sample

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Fix example curl command with structured event

Signed-off-by: Dave Syer <dsyer@vmware.com>
2021-03-08 15:08:16 +01:00
Dave Syer 47bed5616d
Flesh out the docs in the Spring module a bit (#355)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2021-03-08 14:39:17 +01:00
Dave Syer baba37ccfd
Regularize copyright headers (#354)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2021-03-05 10:19:47 +01:00
dependabot[bot] 32bcdcd3b9
Bump nokogiri from 1.10.10 to 1.11.1 in /docs (#347)
Bumps [nokogiri](https://github.com/sparklemotion/nokogiri) from 1.10.10 to 1.11.1.
- [Release notes](https://github.com/sparklemotion/nokogiri/releases)
- [Changelog](https://github.com/sparklemotion/nokogiri/blob/main/CHANGELOG.md)
- [Commits](https://github.com/sparklemotion/nokogiri/compare/v1.10.10...v1.11.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-03-05 09:18:32 +01:00
Dave Syer 8b382734d9
Add support for RSockets with Spring (#349)
* Add support for RSockets with Spring

Also generically can support structured events with any Spring
API that works with Encoder and Decoder. There's a sample for
the RSocket case with a simple request-response echo server.

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Use supported mime types from format provider

Signed-off-by: Dave Syer <dsyer@vmware.com>
2021-03-05 09:18:19 +01:00
Dave Syer f05418cba9
Allow user to enumerate supported content types (#350)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2021-03-01 11:02:54 +01:00
David Denton 23cd08fcfd
Fix #344 - add http4k references to docs (#345)
Signed-off-by: David Denton <denton.david@gmail.com>
2021-02-15 09:42:13 +01:00
github-actions[bot] 70782da2c2
Bump to 2.1.0-SNAPSHOT (#346)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-02-15 09:19:58 +01:00
github-actions[bot] 48fc69e058
Bump to 2.0.0 (#343)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-02-15 09:09:09 +01:00
Mark Scott e7e6e46bd5
fix: prevent NPE on deserializing JSON containing invalid `specversion` value (#342)
* fix: prevent NPE on deserializing JSON containing invalid `specversion` value

Signed-off-by: Mark Scott <mark@codebrewer.org>

* refactor: move test per PR review comment

Signed-off-by: Mark Scott <mark@codebrewer.org>
2021-02-03 08:30:29 +01:00
Francesco Guardiani e523bfbfbf
Rename extension (#339)
* Fixed broken links

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Renamed Extension to CloudEventExtension

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2021-02-02 08:32:43 +01:00
Thomas Qvarnström 12eee4da6e
Use scheduler instead of StartupEvent. Fixes #332 (#333)
Signed-off-by: Thomas Qvarnström <tqvarnst@redhat.com>
2020-12-16 11:36:28 +01:00
Alfusainey Jallow d49ff9f69d
update AMQP docs (#331)
Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>
2020-12-14 11:08:41 +01:00
github-actions[bot] 611f2292a7
Bump to 2.0.0-SNAPSHOT (#329)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-12-11 11:15:02 +01:00
github-actions[bot] 296230719b
Bump to 2.0.0.RC2 (#327)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-12-11 11:04:09 +01:00
Francesco Guardiani d9592d5201
Fixed broken links (#328)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-12-11 10:57:40 +01:00
Francesco Guardiani 00cdf9cb42
Jackson javadocs (#319)
* Jackson javadocs

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Reverted public constructor and deprecated it

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-11 10:45:21 +01:00
Francesco Guardiani bd11010138
Kafka javadocs (#321)
* Kafka javadocs

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Nit

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-11 10:42:43 +01:00
Francesco Guardiani 3a22557b83
Website docs (#324)
* Halfway through it

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* That should be it

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Prettier run

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed code sample

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Suggestions + fixed up the mess made by prettier

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Suggestion

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-11 10:38:04 +01:00
Francesco Guardiani 87c6915d9a
AMQP javadocs (#322)
* Javadocs

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed createReader for structured mode and refactored createReader(String, ApplicationProperties, byte[]) to createReader(String, ApplicationProperties, Section)

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-11 08:41:40 +01:00
Francesco Guardiani a7f87cf6cb
Javadocs to http mods (#320)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-10 19:28:02 +01:00
Francesco Guardiani 711277eacb
Vert.x 4! (#325)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-10 17:51:45 +01:00
Francesco Guardiani 58570cf4d9
Spring Javadocs (#323)
* Spring Javadocs

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Rebase fix

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-10 17:15:07 +01:00
Dave Syer 24d108fe5d
Improve CloudEventHeaderUtils with narrower scoped method (#318)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2020-12-10 10:25:21 +01:00
Francesco Guardiani f5d9b47c1c
Javadocs api and core (#313)
* Javadocs!!!

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Missing module name?

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Excluding javadocs

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* clean install only release artifacts, but verify them all!

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Reverted the crazy idea to use the release profile

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Suggestions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Suggestion

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Nit

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Nit

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Nit

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-09 18:31:56 +01:00
Dave Syer 59643c3368
Add support for Message<CloudEvent> (#315)
* Add support for Message<CloudEvent>

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Add support for structured messages with Spring Message<?>

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Push private classes out to shared utilities

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Resolve some more review comments

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Restructure MessageReader and MessageWriter

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Remove integration test (depends on snapshots still)

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Simplify message converter but drop support for structured format

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Make HTTP optional

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Drop snapshot reporitory declarations

Signed-off-by: Dave Syer <dsyer@vmware.com>
2020-12-09 18:12:09 +01:00
Alfusainey Jallow 34483025df
Fix 404 Not Found in AMQP module Readme (#316)
Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>
2020-12-02 21:28:14 +01:00
dependabot[bot] e9d15daf28
Bump jetty-server in /examples/basic-http (#317)
Bumps [jetty-server](https://github.com/eclipse/jetty.project) from 9.4.30.v20200611 to 9.4.35.v20201120.
- [Release notes](https://github.com/eclipse/jetty.project/releases)
- [Commits](https://github.com/eclipse/jetty.project/compare/jetty-9.4.30.v20200611...jetty-9.4.35.v20201120)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2020-12-02 19:54:54 +01:00
Francesco Guardiani b89f45265b
Simplify the Reader/Writer implementations, reducing the knowledge of spec details (#309)
* Messing up stuff

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Collapse CloudEventAttributesWriter and CloudEventAttributesWriter into CloudEventContextWriter

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Rebase fix

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-12-01 17:27:50 +01:00
Dave Syer a14f5eabec
Add a Spring sample with webflux (#314)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2020-11-30 18:16:20 +01:00
Dave Syer 5099b31f6c
HTTP converters for CloudEvent in Spring (#312)
Supports MVC and WebFlux (blocking and non-blocking) HTTP.
User can work with `CloudEvent` as a `POJO` type and inject it
into `@ReqestMapping` methods.

Signed-off-by: Dave Syer <dsyer@vmware.com>

Co-authored-by: Oleg Zhurakousky <ozhurakousky@pivotal.io>
2020-11-30 11:27:44 +01:00
github-actions[bot] bcc1434a39
Bump to 2.0.0-SNAPSHOT (#310)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-11-26 16:48:45 +01:00
github-actions[bot] c1c55ac1d6
Bump to 2.0.0.RC1 (#305)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-11-26 16:34:41 +01:00
Pierangelo Di Pilato 5ca2c2de91
[Vertx] Fail promise on create reader exception (#307)
When a request contains an invalid event, the body handler doesn't
handle the exception, which leads to an unhandled exception.

Signed-off-by: Pierangelo Di Pilato <pierangelodipilato@gmail.com>
2020-11-26 12:56:08 +01:00
Francesco Guardiani 2524cdf324
Fix NPE on null body with structured message (#306)
* Fix NPE on null body with structured message

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Improved test robustness

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-26 12:49:57 +01:00
Francesco Guardiani eeb83c3567
Bump junit5 (#304)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-26 11:24:39 +01:00
Sreenath Madasu 5a926820b4
Remove UnknownEncodingMessageReader and replace with exceptions #262 (#277)
* Javadoc'ed + Cleanup of the api module (#267)

* Javadoc'ed more and more the api module
Cleanup the CloudEventRWException
More tests on the API module

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Use parseTime

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Better docs on the Extensions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
Signed-off-by: Sreenath Madasu <MADASUSX@legal.regn.net>

* Remove UnknownEncodingMessageReader and replace with exceptions #262

Signed-off-by: Sreenath Madasu <Sreenath.Madasu@gmail.com>

* Remove UnknownEncodingMessageReader and replace with exceptions -- Added Unit tests #262

Signed-off-by: Sreenath Madasu <Sreenath.Madasu@gmail.com>

* Remove UnknownEncodingMessageReader and replace with exceptions -- Fixed compile error #262

Signed-off-by: Sreenath Madasu <Sreenath.Madasu@gmail.com>

* Remove UnknownEncodingMessageReader and replace with exceptions -- changed exception name and details #262

    Signed-off-by: Sreenath Madasu <Sreenath.Madasu@gmail.com>

Signed-off-by: Sreenath Madasu <MADASUSX@legal.regn.net>

* Fixed

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

Co-authored-by: Francesco Guardiani <francescoguard@gmail.com>
Co-authored-by: Sreenath Madasu <MADASUSX@legal.regn.net>
2020-11-26 11:23:24 +01:00
Alfusainey Jallow 7c0b1e3c49
add README.md for AMQP binding (#303)
Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>
2020-11-24 20:21:15 +01:00
Dave Syer 377850cb61
Simplify BaseCloudEventBuilder constructor (#300)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2020-11-24 11:40:15 +01:00
Francesco Guardiani d2a89a4e08
Examples readme (#298)
* Added readme

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Improved the quarkus example

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added amqp proton
Fixed and added stuff to the root readme
Added link to examples readme to the main readme

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-24 10:17:44 +01:00
Pierangelo Di Pilato de786322aa
Bump Vertx from 4.0.0.Beta1 to 4.0.0.RC2 (#299)
Signed-off-by: Pierangelo Di Pilato <pierangelodipilato@gmail.com>
2020-11-24 10:17:20 +01:00
Francesco Guardiani 5037a69a80
Add method `BytesCloudEventData#wrap` (#291)
* Add the method `BytesCloudEventData#wrap` to be consistent with #289

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed all usages of constructor

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-24 09:57:16 +01:00
Francesco Guardiani ceb5a2eeb2
Pom cleanup (#290)
* Fix poms warnings
Bumped javadoc plugin to latest version

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Dumped back the javadoc version

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-24 09:56:42 +01:00
Francesco Guardiani 78a023d08c
Removed some travis leftovers (#295)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-24 09:42:16 +01:00
Alfusainey Jallow bbce65aa64
Add AMQP example (#294)
* Add AMQP example

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>

* close client connection after sending msg

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>

* use Vert.x 4 and refactor according to feedback

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>

* resolve conflicts + move to examples/amqp-proton

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>
2020-11-24 09:38:37 +01:00
Francesco Guardiani cc0892a440
Restful WS Spring boot example (#288)
* WIP Spring boot example

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Applied suggestions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Updated after rebase

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-24 08:43:46 +01:00
Alfusainey Jallow 2d68c4843f
Use constants added in #280 (#282)
Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>
2020-11-24 08:43:12 +01:00
Dave Syer 7f65c92dec
Introduce CloudEventContext (#296)
* Introduce CloudEventContext

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Extract some code into a base class

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Ensure extensions get copied in constructor

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Introduce a CloudEventContextReaderAdapter

Signed-off-by: Dave Syer <dsyer@vmware.com>

* Use inheritance instead of composition

Signed-off-by: Dave Syer <dsyer@vmware.com>
2020-11-23 21:05:26 +01:00
Francesco Guardiani c9b3fa4b65
Move PojoCloudEventData in core (#289)
* Add a static method to wrap a pojo in PojoCloudEventData

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* WIP

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Cleanup

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Reverted BytesCloudEventData and moved to a separate PR

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-23 15:59:33 +01:00
Francesco Guardiani 394347db07
CloudEventAttributesWriter and CloudEventExtensionsWriter accepts only not nil attributes/extensions (#287)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-23 11:28:56 +01:00
Francesco Guardiani 6c78428513
Removing ContextAttributes enums (#280)
* Removed ContextAttributes.java in CloudEventV03

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Rebase changes

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed ContextAttributes.java in CloudEventV1

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Public constants + javadoc

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-20 11:27:36 +01:00
Sung Kyu Park bf60a25098
Update dataref extension value to string to be supported by BaseCloudEvent.readExtensions (#292)
Signed-off-by: kpark <kpark@guidewire.com>

Co-authored-by: kpark <kpark@guidewire.com>
2020-11-20 08:30:23 +01:00
Francesco Guardiani baf3b56d6d
Introduce CloudEventDataMapper.identity() (#286)
* CloudEventDataMapper cannot be null
Cleanup of all mapper != null and replaced with CloudEventDataMapper.NOOP
Fixed all the missing wildcards

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* I forgot a bunch of them

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* CloudEventDataMapper.NOOP to CloudEventDataMapper.identity

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Now it should be fine

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-19 18:24:14 +01:00
Dave Syer 8ac23dc479
Add sources plugin to Maven build (#284)
Signed-off-by: Dave Syer <dsyer@vmware.com>
2020-11-18 11:24:06 +01:00
Arghya Sadhu 84fbb0c801
CloudEventBuilder should fail when providing an invalid extension name (#281)
Signed-off-by: Arghya Sadhu <arghya88@gmail.com>
2020-11-17 12:11:34 +01:00
Alfusainey Jallow 20ebdbf87f
[#30] Implement AMQP 1.0 transport binding (#270)
* [#30] Implement AMQP 1.0 transport binding

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>

* incorporate comments and rename class names.

Now the classes include proton in their names

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>

* incorporate feedback

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>

* fix javadoc issues

Signed-off-by: Alfusainey Jallow <alf.jallow@gmail.com>
2020-11-16 18:01:15 +01:00
Francesco Guardiani 2411fe3508
Fix getAttribute("datacontentencoding") in CloudEventV03 (#279)
Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-16 16:26:58 +01:00
Matej Vasek 554198d9e0
Build javadoc in all profiles (#275)
Signed-off-by: Matej Vasek <mvasek@redhat.com>
2020-11-16 09:46:42 +01:00
Francesco Guardiani c1ff628511
Javadoc'ed + Cleanup of the api module (#267)
* Javadoc'ed more and more the api module
Cleanup the CloudEventRWException
More tests on the API module

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Use parseTime

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Better docs on the Extensions

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-13 14:31:32 +01:00
Matej Vasek 62fe155604
fix javadoc (#274)
Signed-off-by: Matej Vasek <mvasek@redhat.com>
2020-11-13 13:21:38 +01:00
Francesco Guardiani 34408236db
Moved CloudEventUtils from impl to io.cloudevents.core (#261)
Renamed CloudEventUtils#toVisitable to CloudEventUtils#toReader
Added CloudEventUtils#toEvent

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-13 10:00:49 +01:00
Francesco Guardiani 7696ffe4ec
Update website deps to latest version (#265)
Include in the header the GH edit link for any page
Add favicon

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-13 09:58:52 +01:00
Matej Vasek b1aa399b63
feat: new overload for mapper factory (#268)
* feat: new overload for mapper factory

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* test: updates for mapper

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* doc: updated javadoc for PojoCloudEventDataMapper

Signed-off-by: Matej Vasek <mvasek@redhat.com>

* doc: update javadoc

Signed-off-by: Matej Vasek <mvasek@redhat.com>
2020-11-13 09:58:28 +01:00
Francesco Guardiani 42a732623b
Improvements to CloudEventReader (#263)
* Extracted readAttributes and readExtensions from CloudEventReader
Added CloudEventUtils#toContextReader to create a context reader starting from a CloudEvent
Improved documentation of *Reader interfaces
Renamed MessageReader#visit to the proper name MessageReader#read

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Typo

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-13 09:54:07 +01:00
Francesco Guardiani db745fd309
PartitionKey Kafka Interceptor (#260)
* PartitionKey Kafka Interceptor

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Bound

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-13 09:53:55 +01:00
Francesco Guardiani c7baada605
Json mapper (#258)
* Implemented Pojo mapper using jackson
Added other exception kinds

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fixup for the rebase

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed comment

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Equals and hash code

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fixed rebase issues

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-11 08:54:01 +01:00
Francesco Guardiani 3bd9a6922f
CloudEventUtils.mapData(event, mapper) (#257)
* Generified the return value of CloudEventDataMapper
Added toData(CloudEventDataMapper) method to map the data

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Moved to CloudEventUtils

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* public static

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Test

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* wildcard bound

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-11 08:48:45 +01:00
Matej Vasek 118fc82f2b
fix: NPE (#259)
Signed-off-by: Matej Vasek <mvasek@redhat.com>
2020-11-10 18:16:59 +01:00
Francesco Guardiani d09b621409
Cleanup readme (#256)
* Cleanup readme

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Cleanup readme

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Cleanup readme

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-09 14:40:40 +01:00
github-actions[bot] b3cdfc1ded
Bump to 2.0.0-SNAPSHOT (#255)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-11-09 14:13:56 +01:00
github-actions[bot] f52356e93a
Bump to 2.0.0-milestone4 (#254)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-11-09 14:06:44 +01:00
Francesco Guardiani de2052c4d6
CloudEventDataMapper (#252)
* Defined an interface to perform mapping of data

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* All code compiles and run

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Added a sample + fixed copyrights

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* More test

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-11-09 09:37:37 +01:00
Francesco Guardiani 5e747e7278
Introduce JsonCloudEventData (#251)
* Implemented JsonCloudEventData

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Suggestion

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-10-29 15:59:15 +01:00
Francesco Guardiani f9e31efaa3
Introduce CloudEventData (#250)
* Introduce CloudEventData

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Javadocs

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Removed to

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Applied changes throughout the sdk

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Fix javadoc

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-10-29 10:01:39 +01:00
Zhixuan Lai a09b03bd2f
Serialize time to RFC3339 compliant string (#249)
Signed-off-by: Zhixuan Lai <zhixuan@squareup.com>

Co-authored-by: Zhixuan Lai <zhixuan@squareup.com>
2020-09-30 13:03:13 +02:00
Francesco Guardiani f219b6937c
Release process docs (#248)
* Doc of the new release process using GH actions and cleanup of the old release scripts

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>

* Nit

Signed-off-by: Francesco Guardiani <francescoguard@gmail.com>
2020-09-29 13:35:57 +02:00
github-actions[bot] 3cea0245b3
Bump to 2.0.0-SNAPSHOT (#247)
Signed-off-by: GitHub <noreply@github.com>

Co-authored-by: slinkydeveloper <slinkydeveloper@users.noreply.github.com>
2020-09-29 12:54:27 +02:00
461 changed files with 22590 additions and 2136 deletions

11
.github/dependabot.yml vendored Normal file
View File

@ -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"

View File

@ -4,6 +4,8 @@ on:
push:
branches:
- master
- main
- '[0-9]+.[0-9]+'
jobs:
test:
@ -11,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ 8, 11 ]
java: [ 8, 11, 17 ]
steps:
- uses: actions/checkout@v2
- name: Setup java
@ -19,8 +21,9 @@ jobs:
with:
java-version: ${{ matrix.java }}
- run: |
mvn clean install -DskipTests -B
mvn verify
./mvnw clean install -DskipTests -B
./mvnw verify -B
deploy:
runs-on: ubuntu-latest
name: Deploy
@ -31,13 +34,13 @@ jobs:
uses: actions/setup-java@v1
with:
java-version: 8
server-id: ossrh # Value of the distributionManagement/repository/id field of the pom.xml
server-id: central # Value of the distributionManagement/repository/id field of the pom.xml
server-username: MAVEN_USERNAME # env variable for username in deploy
server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
- name: Publish to Apache Maven Central
run: mvn clean deploy -Drelease -DskipTests -B
run: ./mvnw clean deploy -Drelease -DskipTests -B
env:
MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }}
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}

View File

@ -5,6 +5,14 @@ on:
version:
description: 'Version to bump (without prepending "v")'
required: true
maven-modules:
description: "Whether to bump versions in pom.xml files"
type: choice
required: true
default: 'true'
options:
- 'true'
- 'false'
jobs:
bump:
@ -19,7 +27,8 @@ jobs:
with:
java-version: 8
- name: Bump version using Maven
run: 'mvn versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -B'
if: ${{ inputs.maven-modules == 'true' }}
run: './mvnw versions:set -DnewVersion=$NEW_VERSION -DgenerateBackupPoms=false -B'
- name: Bump version in docs
if: ${{ !endsWith(github.event.inputs.version, 'SNAPSHOT') }}
run: 'find . -type f -name "*.md" -exec sed -i -e "s+<version>[a-zA-Z0-9.-]*<\/version>+<version>$NEW_VERSION</version>+g" {} +'

View File

@ -4,20 +4,24 @@ on:
pull_request:
branches:
- master
- main
- '[0-9]+.[0-9]+'
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
java: [ 8, 11 ]
java: [ 8, 11, 17 ]
name: Java ${{ matrix.java }} Test
steps:
- uses: actions/checkout@v2
- name: Setup java
uses: actions/setup-java@v1
uses: actions/setup-java@v2
with:
java-version: ${{ matrix.java }}
distribution: 'temurin'
- run: |
mvn clean install -DskipTests -B
mvn verify
./mvnw clean install -DskipTests -B
./mvnw verify -B
./mvnw javadoc:javadoc

7
.gitignore vendored
View File

@ -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/*

View File

@ -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.

5
MAINTAINERS.md Normal file
View File

@ -0,0 +1,5 @@
# Maintainers
Current active maintainers of this SDK:
- [Pierangelo Di Pilato](https://github.com/pierDipi)

View File

@ -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.

103
README.md
View File

@ -1,84 +1,123 @@
# Java SDK for CloudEvents API
[![Build Status](https://travis-ci.org/cloudevents/sdk-java.png)](https://travis-ci.org/cloudevents/sdk-java)
[![License](https://img.shields.io/:license-Apache2-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.cloudevents/cloudevents-parent/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.cloudevents/cloudevents-parent)
[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-core.svg?color=green)](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](https://github.com/cloudevents/sdk-java/tree/1.x)
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: |
| - [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
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).
<sub>† Source/artifacts hosted externally</sub>
## Documentation
Documentation is available at https://cloudevents.github.io/sdk-java/
Documentation is available at https://cloudevents.github.io/sdk-java/.
Javadocs are available on [javadoc.io](https://www.javadoc.io):
* [cloudevents-api](https://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
* [cloudevents-core](https://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
* [cloudevents-json-jackson](https://www.javadoc.io/doc/io.cloudevents/cloudevents-json-jackson)
* [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-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
<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>
- [Knative Eventing](https://github.com/knative-sandbox/eventing-kafka-broker)
| [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#)
- 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).
- 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/master/community)
directory. In particular, in there you will find information concerning
how SDK projects are
[managed](https://github.com/cloudevents/spec/blob/master/community/SDK-GOVERNANCE.md),
[guidelines](https://github.com/cloudevents/spec/blob/master/community/SDK-maintainer-guidelines.md)
[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/master/community/GOVERNANCE.md#additional-information)
[Code of Conduct](https://github.com/cloudevents/spec/blob/main/docs/GOVERNANCE.md#additional-information)
information.
If there is a security concern with one of the CloudEvents specifications, or
with one of the project's SDKs, please send an email to
[cncf-cloudevents-security@lists.cncf.io](mailto:cncf-cloudevents-security@lists.cncf.io).
## Additional SDK Resources
- [List of current active maintainers](MAINTAINERS.md)
- [How to contribute to the project](CONTRIBUTING.md)
- [SDK's License](LICENSE)
- [SDK's Release process](RELEASING.md)
- [SDK Maintainer's guide](MAINTAINERS_GUIDE.md)
[http4k]: https://www.http4k.org/guide/reference/cloud_events/

12
RELEASING.md Normal file
View File

@ -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.

5
amqp/README.md Normal file
View File

@ -0,0 +1,5 @@
# AMQP Protocol Binding
Javadoc: [![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-amqp-proton.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-amqp-proton)
Documentation: https://cloudevents.github.io/sdk-java/amqp-proton

64
amqp/pom.xml Normal file
View File

@ -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>

View File

@ -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<>();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}
}

View File

@ -1,28 +1,5 @@
# CloudEvents API
[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-api.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
Javadoc: [![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-api.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-api)
For Maven based projects, use the following dependency:
```xml
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-api</artifactId>
<version>2.0.0-milestone3</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

View File

@ -24,12 +24,11 @@
<parent>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-parent</artifactId>
<version>2.0.0-milestone3</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>

View File

@ -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();
}

View File

@ -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 {
}

View File

@ -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();
}

View File

@ -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

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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

View File

@ -1,63 +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.lang.Nullable;
import io.cloudevents.types.Time;
import java.net.URI;
import java.time.OffsetDateTime;
/**
* Interface to write the attributes from a {@link io.cloudevents.rw.CloudEventReader} to a new representation.
*/
public interface CloudEventAttributesWriter {
/**
* Set attribute with type {@link String}. This setter should not be invoked for specversion, because the built Visitor already
* has the information through the {@link CloudEventWriterFactory}.
*
* @param name
* @param value
* @throws CloudEventRWException
*/
CloudEventAttributesWriter withAttribute(String name, @Nullable String value) throws CloudEventRWException;
/**
* Set attribute with type {@link URI}.
*
* @param name
* @param value
* @throws CloudEventRWException
*/
default CloudEventAttributesWriter withAttribute(String name, @Nullable URI value) throws CloudEventRWException {
return withAttribute(name, value == null ? null : value.toString());
}
/**
* Set attribute with type {@link OffsetDateTime} attribute.
*
* @param name
* @param value
* @throws CloudEventRWException
*/
default CloudEventAttributesWriter withAttribute(String name, @Nullable OffsetDateTime value) throws CloudEventRWException {
return withAttribute(name, value == null ? null : Time.writeTime(value));
}
}

View File

@ -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;
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -1,60 +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.lang.Nullable;
import java.net.URI;
/**
* Interface to write the extensions from a {@link io.cloudevents.rw.CloudEventReader} to a new representation.
*/
public interface CloudEventExtensionsWriter {
/**
* Set an extension with type {@link String}.
*
* @param name
* @param value
* @throws CloudEventRWException
*/
CloudEventExtensionsWriter withExtension(String name, @Nullable String value) throws CloudEventRWException;
/**
* Set attribute with type {@link URI}.
*
* @param name
* @param value
* @throws CloudEventRWException
*/
default CloudEventExtensionsWriter withExtension(String name, @Nullable Number value) throws CloudEventRWException {
return withExtension(name, value == null ? null : value.toString());
}
/**
* Set attribute with type {@link Boolean} attribute.
*
* @param name
* @param value
* @throws CloudEventRWException
*/
default CloudEventExtensionsWriter withExtension(String name, @Nullable Boolean value) throws CloudEventRWException {
return withExtension(name, value == null ? null : value.toString());
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -17,9 +17,14 @@
package io.cloudevents.types;
import io.cloudevents.rw.CloudEventRWException;
import java.time.DateTimeException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;
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>
*/
@ -29,16 +34,56 @@ public final class Time {
}
/**
* Parse a {@link String} RFC3339 compliant as {@link OffsetDateTime}
* 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 OffsetDateTime parseTime(String time) throws DateTimeParseException {
return OffsetDateTime.parse(time);
}
/**
* Convert a {@link OffsetDateTime} 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(OffsetDateTime time) throws DateTimeParseException {
return time.toString();
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);
}
}
}

View File

@ -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())
);
}
}

View File

@ -17,6 +17,7 @@
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;
@ -28,6 +29,7 @@ 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 {
@ -44,12 +46,19 @@ 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+02:00");
))).isEqualTo("2020-08-03T18:10:00+02:00");
}
public static Stream<Arguments> parseDateArguments() {

View File

@ -21,7 +21,7 @@
<parent>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-parent</artifactId>
<version>2.0.0-milestone3</version>
<version>4.1.0-SNAPSHOT</version>
</parent>
<artifactId>cloudevents-benchmarks</artifactId>
@ -34,6 +34,7 @@
<javac.target>1.8</javac.target>
<!-- Name of the benchmark Uber-JAR to generate. -->
<uberjar.name>benchmarks</uberjar.name>
<maven.javadoc.skip>true</maven.javadoc.skip>
</properties>
<dependencies>
@ -58,6 +59,11 @@
<artifactId>cloudevents-kafka</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-sql</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openjdk.jmh</groupId>
@ -135,7 +141,7 @@
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.1</version>
<version>3.1.3</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
@ -155,7 +161,7 @@
</plugin>
<plugin>
<artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>

View File

@ -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")
);
}
}

View File

@ -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)
);
}
}

91
bom/pom.xml Normal file
View File

@ -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>

View File

@ -1,67 +1,5 @@
# CloudEvents Core
[![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-core.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
Javadoc: [![Javadocs](http://www.javadoc.io/badge/io.cloudevents/cloudevents-core.svg?color=green)](http://www.javadoc.io/doc/io.cloudevents/cloudevents-core)
The base classes, interfaces and low-level APIs to use CloudEvents.
## How to Use
For Maven based projects, use the following dependency:
```xml
<dependency>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-api</artifactId>
<version>2.0.0-milestone3</version>
</dependency>
```
### Create an Event
```java
import io.cloudevents.CloudEvent;
import io.cloudevents.core.builder.CloudEventBuilder;
import java.net.URI;
final CloudEvent event = CloudEventBuilder.v1()
.withId("000")
.withType("example.demo")
.withSource(URI.create("http://example.com"))
.withData("application/json", "{}".getBytes())
.build();
```
### Materialize an Extension
CloudEvent extensions can be materialized in their respective POJOs:
```java
import io.cloudevents.core.extensions.DistributedTracingExtension;
import io.cloudevents.core.extensions.ExtensionsParser;
DistributedTracingExtension dte = ExtensionsParser.getInstance()
.parseExtension(DistributedTracingExtension.class, event);
```
### Using Event Formats
The SDK implements [Event Formats](https://github.com/cloudevents/spec/blob/v1.0/spec.md#event-format) in submodules.
To use them, you just need to add them as dependencies to your project and the SDK,
through the `ServiceLoader` mechanism, will load them into the classpath.
For example, to use the [JSON event format](https://github.com/cloudevents/spec/blob/v1.0/json-format.md) with Jackson,
add `cloudevents-json-jackson` as a dependency and then:
```java
import io.cloudevents.core.format.EventFormatProvider;
import io.cloudevents.jackson.JsonFormat;
EventFormat format = EventFormatProvider
.getInstance()
.resolveFormat(JsonFormat.CONTENT_TYPE);
// Serialize event
byte[] serialized = format.serialize(event);
// Deserialize event
CloudEvent event = format.deserialize(bytes);
```
Documentation: https://cloudevents.github.io/sdk-java/core

View File

@ -22,12 +22,11 @@
<parent>
<groupId>io.cloudevents</groupId>
<artifactId>cloudevents-parent</artifactId>
<version>2.0.0-milestone3</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>

View File

@ -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);
}
}

View File

@ -17,9 +17,7 @@
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;
@ -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,8 +170,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
* @param value value of the extension attribute
* @return self
*/
@Override
CloudEventBuilder withExtension(@Nonnull String key, String value);
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull String value);
/**
* Set an extension with provided key and numeric value
@ -133,8 +179,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
* @param value value of the extension attribute
* @return self
*/
@Override
CloudEventBuilder withExtension(@Nonnull String key, Number value);
CloudEventBuilder withExtension(@Nonnull String key, @Nonnull Number value);
/**
* Set an extension with provided key and boolean value
@ -143,8 +188,34 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
* @param value value of the extension attribute
* @return self
*/
@Override
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
@ -152,7 +223,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
* @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
@ -168,7 +239,7 @@ public interface CloudEventBuilder extends CloudEventWriter<CloudEvent> {
* @param extension materialized extension to remove from the builder
* @return self
*/
CloudEventBuilder withoutExtension(@Nonnull Extension extension);
CloudEventBuilder withoutExtension(@Nonnull CloudEventExtension extension);
/**
* Build the event
@ -233,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."
);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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

View File

@ -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());
}
}

View File

@ -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 + "\"");
}
}

View File

@ -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(); }
}

View File

@ -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);
}
}

View File

@ -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 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

View File

@ -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);
}
}

View File

@ -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.withExtension(entry.getKey(), (String) entry.getValue());
writer.withContextAttribute(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof Number) {
visitor.withExtension(entry.getKey(), (Number) entry.getValue());
writer.withContextAttribute(entry.getKey(), (Number) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
visitor.withExtension(entry.getKey(), (Boolean) entry.getValue());
writer.withContextAttribute(entry.getKey(), (Boolean) entry.getValue());
} else if (entry.getValue() instanceof URI) {
writer.withContextAttribute(entry.getKey(), (URI) entry.getValue());
} else if (entry.getValue() instanceof OffsetDateTime) {
writer.withContextAttribute(entry.getKey(), (OffsetDateTime) entry.getValue());
} else if (entry.getValue() instanceof byte[]) {
writer.withContextAttribute(entry.getKey(), (byte[]) entry.getValue());
} else {
// This should never happen because we build that map only through our builders
throw new IllegalStateException("Illegal value inside extensions map: " + entry);

View File

@ -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,17 +80,100 @@ 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 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;
}
@ -98,12 +185,12 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
}
@Override
public SELF withoutExtension(@Nonnull Extension extension) {
public SELF withoutExtension(@Nonnull CloudEventExtension extension) {
extension.getKeys().forEach(this::withoutExtension);
return self;
}
public SELF withExtension(@Nonnull Extension extension) {
public SELF withExtension(@Nonnull CloudEventExtension extension) {
for (String key : extension.getKeys()) {
Object value = extension.getValue(key);
if (value != null) {
@ -114,7 +201,7 @@ public abstract class BaseCloudEventBuilder<SELF extends BaseCloudEventBuilder<S
}
@Override
public CloudEvent end(byte[] value) throws CloudEventRWException {
public CloudEvent end(CloudEventData value) throws CloudEventRWException {
this.data = value;
return build();
}
@ -131,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");
}
}
}

View File

@ -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);
}
}

View File

@ -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.withAttribute("id", event.getId());
writer.withAttribute("source", event.getSource());
writer.withAttribute("type", event.getType());
if (event.getDataContentType() != null) {
writer.withAttribute("datacontenttype", event.getDataContentType());
}
if (event.getDataSchema() != null) {
writer.withAttribute("dataschema", event.getDataSchema());
}
if (event.getSubject() != null) {
writer.withAttribute("subject", event.getSubject());
}
if (event.getTime() != null) {
writer.withAttribute("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.withExtension(key, (String) value);
} else if (value instanceof Number) {
visitor.withExtension(key, (Number) value);
} else if (value instanceof Boolean) {
visitor.withExtension(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);
}
}
;
}
}

View File

@ -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());
}
}

View File

@ -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
}

View File

@ -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
* @throws CloudEventRWException if something went wrong during the visit.
* @throws IllegalStateException if the message is not in binary 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
* @param <R> the return type of the {@link StructuredMessageWriter}
* @param writer Structured Message writer
* @throws CloudEventRWException if something went wrong during the visit.
* @throws IllegalStateException if the message is not in structured encoding.
*/
<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.
*/
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."
);
}
}

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 (isContentTypeHeader(key)) {
visitor.withAttribute("datacontenttype", toCloudEventsValue(value));
} else if (isCloudEventsHeader(key)) {
String name = toCloudEventsKey(key);
if (name.equals("specversion")) {
if (value == null) {
return;
}
if (this.version.getAllAttributes().contains(name)) {
visitor.withAttribute(name, toCloudEventsValue(value));
} else {
visitor.withExtension(name, toCloudEventsValue(value));
if (isContentTypeHeader(key)) {
visitor.withContextAttribute(CloudEventV1.DATACONTENTTYPE, toCloudEventsValue(value));
} else if (isCloudEventsHeader(key)) {
String name = toCloudEventsKey(key);
if (name.equals(CloudEventV1.SPECVERSION)) {
return;
}
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.withAttribute("datacontenttype", toCloudEventsValue(value));
} else if (isCloudEventsHeader(key)) {
String name = toCloudEventsKey(key);
if (name.equals("specversion")) {
return;
}
if (this.version.getAllAttributes().contains(name)) {
writer.withAttribute(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.withExtension(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);
}

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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);
}

View File

@ -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");
}
}

View File

@ -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);
}
}
}

View File

@ -17,20 +17,27 @@
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 {
@ -51,10 +58,8 @@ public final class EventFormatProvider {
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);
}
/**
@ -68,6 +73,17 @@ public final class EventFormatProvider {
}
}
/**
* 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.
*
@ -83,4 +99,14 @@ public final class EventFormatProvider {
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());
}
}

View File

@ -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 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;
}

View File

@ -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.OffsetDateTime;
import java.time.format.DateTimeParseException;
import static io.cloudevents.core.v03.CloudEventV03.*;
/**
* CloudEvent V0.3 builder.
@ -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));
}
}
@ -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 CloudEventBuilder withAttribute(String name, String value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
requireValidAttributeWrite(name);
switch (name) {
case "id":
case ID:
withId(value);
return this;
case "source":
case SOURCE:
try {
withSource(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
}
return this;
case "type":
case TYPE:
withType(value);
return this;
case "datacontenttype":
case DATACONTENTTYPE:
withDataContentType(value);
return this;
case "datacontentencoding":
case DATACONTENTENCODING:
// No-op, this information is not saved in the event because it's useful only for parsing
return this;
case "schemaurl":
case SCHEMAURL:
try {
withSchemaUrl(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("schemaurl", value, e);
throw CloudEventRWException.newInvalidAttributeValue(SCHEMAURL, value, e);
}
return this;
case "subject":
case SUBJECT:
withSubject(value);
return this;
case "time":
try {
withTime(Time.parseTime(value));
} catch (DateTimeParseException e) {
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
}
case TIME:
withTime(Time.parseTime(TIME, value));
return this;
default:
withExtension(name, value);
return this;
}
throw CloudEventRWException.newInvalidAttributeName(name);
}
@Override
public CloudEventBuilder withAttribute(String name, URI value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
requireValidAttributeWrite(name);
switch (name) {
case "source":
case SOURCE:
withSource(value);
return this;
case "schemaurl":
case SCHEMAURL:
withDataSchema(value);
return this;
}
case ID:
case TYPE:
case DATACONTENTTYPE:
case DATACONTENTENCODING:
case SUBJECT:
case TIME:
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
default:
withExtension(name, value);
return this;
}
}
@Override
public CloudEventBuilder withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
if ("time".equals(name)) {
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;
}
}
}

View File

@ -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.OffsetDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@ -36,6 +36,51 @@ 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;
@ -47,7 +92,7 @@ public final class CloudEventV03 extends BaseCloudEvent {
public CloudEventV03(String id, URI source, String type,
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;
@ -100,64 +145,68 @@ public final class CloudEventV03 extends BaseCloudEvent {
@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.withAttribute(
ContextAttributes.ID.name().toLowerCase(),
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
writer.withContextAttribute(
ID,
this.id
);
writer.withAttribute(
ContextAttributes.SOURCE.name().toLowerCase(),
writer.withContextAttribute(
SOURCE,
this.source
);
writer.withAttribute(
ContextAttributes.TYPE.name().toLowerCase(),
writer.withContextAttribute(
TYPE,
this.type
);
if (this.datacontenttype != null) {
writer.withAttribute(
ContextAttributes.DATACONTENTTYPE.name().toLowerCase(),
writer.withContextAttribute(
DATACONTENTTYPE,
this.datacontenttype
);
}
if (this.schemaurl != null) {
writer.withAttribute(
ContextAttributes.SCHEMAURL.name().toLowerCase(),
writer.withContextAttribute(
SCHEMAURL,
this.schemaurl
);
}
if (this.subject != null) {
writer.withAttribute(
ContextAttributes.SUBJECT.name().toLowerCase(),
writer.withContextAttribute(
SUBJECT,
this.subject
);
}
if (this.time != null) {
writer.withAttribute(
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 +
'}';
}
}

View File

@ -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();
}
}

View File

@ -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.OffsetDateTime;
import java.time.format.DateTimeParseException;
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 V1ToV03AttributesConverter withAttribute(String name, String value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
switch (name) {
case "id":
case ID:
builder.withId(value);
return this;
case "source":
case SOURCE:
try {
builder.withSource(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
}
return this;
case "type":
case TYPE:
builder.withType(value);
return this;
case "datacontenttype":
case DATACONTENTTYPE:
builder.withDataContentType(value);
return this;
case "dataschema":
case DATASCHEMA:
try {
builder.withSchemaUrl(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
throw CloudEventRWException.newInvalidAttributeValue(DATASCHEMA, value, e);
}
return this;
case "subject":
case SUBJECT:
builder.withSubject(value);
return this;
case "time":
try {
builder.withTime(Time.parseTime(value));
} catch (DateTimeParseException e) {
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
}
case TIME:
builder.withTime(Time.parseTime(TIME, value));
return this;
default:
builder.withExtension(name, value);
return this;
}
throw CloudEventRWException.newInvalidAttributeName(name);
}
@Override
public V1ToV03AttributesConverter withAttribute(String name, URI value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
switch (name) {
case "source":
case SOURCE:
builder.withSource(value);
return this;
case "dataschema":
case DATASCHEMA:
builder.withSchemaUrl(value);
return this;
}
case ID:
case TYPE:
case DATACONTENTTYPE:
case SUBJECT:
case TIME:
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
default:
builder.withExtension(name, value);
return this;
}
}
@Override
public V1ToV03AttributesConverter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
if ("time".equals(name)) {
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;
}
}
}

View File

@ -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.OffsetDateTime;
import java.time.format.DateTimeParseException;
import static io.cloudevents.core.v1.CloudEventV1.*;
/**
* CloudEvent V1.0 builder.
@ -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));
}
}
@ -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 CloudEventBuilder withAttribute(String name, String value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
requireValidAttributeWrite(name);
switch (name) {
case "id":
case ID:
withId(value);
return this;
case "source":
case SOURCE:
try {
withSource(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
}
return this;
case "type":
case TYPE:
withType(value);
return this;
case "datacontenttype":
case DATACONTENTTYPE:
withDataContentType(value);
return this;
case "dataschema":
case DATASCHEMA:
try {
withDataSchema(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
throw CloudEventRWException.newInvalidAttributeValue(DATASCHEMA, value, e);
}
return this;
case "subject":
case SUBJECT:
withSubject(value);
return this;
case "time":
try {
withTime(Time.parseTime(value));
} catch (DateTimeParseException e) {
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
}
case TIME:
withTime(Time.parseTime(TIME, value));
return this;
default:
withExtension(name, value);
return this;
}
throw CloudEventRWException.newInvalidAttributeName(name);
}
@Override
public CloudEventBuilder withAttribute(String name, URI value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
requireValidAttributeWrite(name);
switch (name) {
case "source":
case SOURCE:
withSource(value);
return this;
case "dataschema":
case DATASCHEMA:
withDataSchema(value);
return this;
}
case ID:
case TYPE:
case DATACONTENTTYPE:
case SUBJECT:
case TIME:
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
default:
withExtension(name, value);
return this;
}
}
@Override
public CloudEventBuilder withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
if ("time".equals(name)) {
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;
}
}
}

View File

@ -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.OffsetDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@ -35,6 +35,46 @@ 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;
@ -46,7 +86,7 @@ public final class CloudEventV1 extends BaseCloudEvent {
public CloudEventV1(String id, URI source,
String type, String datacontenttype,
URI dataschema, String subject, OffsetDateTime time,
byte[] data, Map<String, Object> extensions) {
CloudEventData data, Map<String, Object> extensions) {
super(data, extensions);
this.id = id;
@ -95,64 +135,65 @@ public final class CloudEventV1 extends BaseCloudEvent {
@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.withAttribute(
ContextAttributes.ID.name().toLowerCase(),
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
writer.withContextAttribute(
ID,
this.id
);
writer.withAttribute(
ContextAttributes.SOURCE.name().toLowerCase(),
writer.withContextAttribute(
SOURCE,
this.source
);
writer.withAttribute(
ContextAttributes.TYPE.name().toLowerCase(),
writer.withContextAttribute(
TYPE,
this.type
);
if (this.datacontenttype != null) {
writer.withAttribute(
ContextAttributes.DATACONTENTTYPE.name().toLowerCase(),
writer.withContextAttribute(
DATACONTENTTYPE,
this.datacontenttype
);
}
if (this.dataschema != null) {
writer.withAttribute(
ContextAttributes.DATASCHEMA.name().toLowerCase(),
writer.withContextAttribute(
DATASCHEMA,
this.dataschema
);
}
if (this.subject != null) {
writer.withAttribute(
ContextAttributes.SUBJECT.name().toLowerCase(),
writer.withContextAttribute(
SUBJECT,
this.subject
);
}
if (this.time != null) {
writer.withAttribute(
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 +
'}';
}

View File

@ -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.OffsetDateTime;
import java.time.format.DateTimeParseException;
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 V03ToV1AttributesConverter withAttribute(String name, String value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
switch (name) {
case "id":
case ID:
builder.withId(value);
return this;
case "source":
case SOURCE:
try {
builder.withSource(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("source", value, e);
throw CloudEventRWException.newInvalidAttributeValue(SOURCE, value, e);
}
return this;
case "type":
case TYPE:
builder.withType(value);
return this;
case "datacontenttype":
case DATACONTENTTYPE:
builder.withDataContentType(value);
return this;
case "schemaurl":
case SCHEMAURL:
try {
builder.withDataSchema(new URI(value));
} catch (URISyntaxException e) {
throw CloudEventRWException.newInvalidAttributeValue("dataschema", value, e);
throw CloudEventRWException.newInvalidAttributeValue(SCHEMAURL, value, e);
}
return this;
case "subject":
case SUBJECT:
builder.withSubject(value);
return this;
case "time":
try {
builder.withTime(Time.parseTime(value));
} catch (DateTimeParseException e) {
throw CloudEventRWException.newInvalidAttributeValue("time", value, e);
}
case TIME:
builder.withTime(Time.parseTime(TIME, value));
return this;
default:
builder.withExtension(name, value);
return this;
}
throw CloudEventRWException.newInvalidAttributeName(name);
}
@Override
public V03ToV1AttributesConverter withAttribute(String name, URI value) throws CloudEventRWException {
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
switch (name) {
case "source":
case SOURCE:
builder.withSource(value);
return this;
case "schemaurl":
case SCHEMAURL:
builder.withDataSchema(value);
return this;
}
case ID:
case TYPE:
case DATACONTENTTYPE:
case SUBJECT:
case TIME:
throw CloudEventRWException.newInvalidAttributeType(name, URI.class);
default:
builder.withExtension(name, value);
return this;
}
}
@Override
public V03ToV1AttributesConverter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
if ("time".equals(name)) {
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;
}
}
}

View File

@ -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() {}
/**
* Convert a {@link CloudEvent} to a {@link CloudEventReader}. This method provides a default implementation
* for CloudEvent that doesn't implement CloudEventVisitable
*
* @param event the event to convert
* @return the visitable implementation
* @author Vinay Bhat
* Interface which defines validation for CloudEvents attributes and extensions.
*/
public static CloudEventReader toVisitable(CloudEvent event) {
if (event instanceof CloudEventReader) {
return (CloudEventReader) event;
} else {
return new CloudEventReaderAdapter(event);
}
}
public interface CloudEventValidator {
/**
* Validate the attributes of a CloudEvent.
*
* @param cloudEvent the CloudEvent to validate
*/
void validate(CloudEvent cloudEvent);
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -42,7 +42,7 @@ public class DatarefExtensionTest {
.build();
assertThat(event.getExtension(DatarefExtension.DATAREF))
.isEqualTo(URI.create("http://example"));
.isEqualTo("http://example");
}
@Test

View File

@ -6,7 +6,11 @@ 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 {
@ -45,4 +49,94 @@ public class BaseCloudEventBuilderTest {
.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()
);
}
}

View File

@ -91,4 +91,28 @@ public class CloudEventImplTest {
);
}
@Test
public void testToStringV1() {
CloudEvent event = CloudEventBuilder.v1()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.build();
assertThat(event.toString())
.doesNotContain("time");
}
@Test
public void testToStringV03() {
CloudEvent event = CloudEventBuilder.v03()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.build();
assertThat(event.toString())
.doesNotContain("time");
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2018-Present The CloudEvents Authors
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.cloudevents.core.impl;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
public class StringUtilsTest {
@ParameterizedTest
@MethodSource("startsWithIgnoreCaseArgs")
public void startsWithIgnoreCase(final String s, final String prefix, final boolean expected) {
Assertions.assertThat(StringUtils.startsWithIgnoreCase(s, prefix)).isEqualTo(expected);
}
private static Stream<Arguments> startsWithIgnoreCaseArgs() {
return Stream.of(
Arguments.of("s", "s", true),
Arguments.of("sa", "S", true),
Arguments.of("saS", "As", false),
Arguments.of("sasso", "SASO", false),
Arguments.of("sasso", "SaSsO", true)
);
}
}

View File

@ -0,0 +1,83 @@
package io.cloudevents.core.message.impl;
import io.cloudevents.SpecVersion;
import io.cloudevents.core.mock.CSVFormat;
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.util.stream.Stream;
import static io.cloudevents.SpecVersion.V03;
import static io.cloudevents.SpecVersion.V1;
import static io.cloudevents.core.message.impl.MessageUtils.parseStructuredOrBinaryMessage;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
class MessageUtilsTest {
@Test
void parseStructuredOrBinaryMessage_Exception() {
final CloudEventRWException cloudEventRWException = assertThrows(CloudEventRWException.class, () -> {
parseStructuredOrBinaryMessage(() -> null, eventFormat -> null, () -> null, specVersion -> null);
});
assertThat(cloudEventRWException.getKind())
.isEqualTo(CloudEventRWException.CloudEventRWExceptionKind.UNKNOWN_ENCODING);
}
/**
* Verify an exception is thrown if an unsupported
* application/cloudevents content-type family is
* received.
*/
@ParameterizedTest
@MethodSource
void testBadContentTypes(String contentType) {
CloudEventRWException exception = assertThrows(CloudEventRWException.class, () ->
{
parseStructuredOrBinaryMessage(() -> contentType, eventFormat -> null, () -> "1.0", specVersion -> null);
});
assertThat(exception.getKind()).isEqualTo(CloudEventRWException.CloudEventRWExceptionKind.UNKNOWN_ENCODING);
}
@Test
void testParseStructuredOrBinaryMessage_StructuredMode() {
MessageUtils.parseStructuredOrBinaryMessage(() -> "application/cloudevents+csv;",
eventFormat -> {
assertTrue(eventFormat instanceof CSVFormat);
return null;
},
() -> null, specVersion -> null);
}
@ParameterizedTest
@MethodSource
void testParseStructuredOrBinaryMessage_BinaryMode(String specVersionHeader, SpecVersion expectedSpecVersion) {
MessageUtils.parseStructuredOrBinaryMessage(() -> null, eventFormat -> null,
() -> specVersionHeader, specVersion -> {
assertEquals(expectedSpecVersion, specVersion);
return null;
});
}
private static Stream<Arguments> testParseStructuredOrBinaryMessage_BinaryMode() {
return Stream.of(
Arguments.of("0.3", V03),
Arguments.of("1.0", V1)
);
}
private static Stream<Arguments> testBadContentTypes() {
return Stream.of(
Arguments.of("application/cloudevents"),
Arguments.of("application/cloudevents+morse")
);
}
}

View File

@ -20,7 +20,9 @@ package io.cloudevents.core.mock;
import io.cloudevents.CloudEvent;
import io.cloudevents.SpecVersion;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.data.BytesCloudEventData;
import io.cloudevents.core.format.EventFormat;
import io.cloudevents.rw.CloudEventDataMapper;
import io.cloudevents.types.Time;
import java.net.URI;
@ -51,13 +53,13 @@ public class CSVFormat implements EventFormat {
? Time.writeTime(event.getTime())
: "null",
event.getData() != null
? new String(Base64.getEncoder().encode(event.getData()), StandardCharsets.UTF_8)
? new String(Base64.getEncoder().encode(event.getData().toBytes()), StandardCharsets.UTF_8)
: "null"
).getBytes();
).getBytes(StandardCharsets.UTF_8);
}
@Override
public CloudEvent deserialize(byte[] bytes) {
public CloudEvent deserialize(byte[] bytes, CloudEventDataMapper mapper) {
String[] splitted = new String(bytes, StandardCharsets.UTF_8).split(Pattern.quote(","));
SpecVersion sv = SpecVersion.parse(splitted[0]);
@ -68,9 +70,9 @@ public class CSVFormat implements EventFormat {
URI dataschema = splitted[5].equals("null") ? null : URI.create(splitted[5]);
String subject = splitted[6].equals("null") ? null : splitted[6];
OffsetDateTime time = splitted[7].equals("null") ? null : Time.parseTime(splitted[7]);
byte[] data = splitted[8].equals("null") ? null : Base64.getDecoder().decode(splitted[8].getBytes());
byte[] data = splitted[8].equals("null") ? null : Base64.getDecoder().decode(splitted[8].getBytes(StandardCharsets.UTF_8));
io.cloudevents.core.v1.CloudEventBuilder builder = CloudEventBuilder.v1()
CloudEventBuilder builder = CloudEventBuilder.fromSpecVersion(sv)
.withId(id)
.withType(type)
.withSource(source);
@ -88,16 +90,10 @@ public class CSVFormat implements EventFormat {
builder.withTime(time);
}
if (data != null) {
builder.withData(data);
builder.withData(mapper.map(BytesCloudEventData.wrap(data)));
}
switch (sv) {
case V03:
return CloudEventBuilder.v03(builder.build()).build();
case V1:
return builder.build();
}
return null;
}
@Override
public Set<String> deserializableContentTypes() {

View File

@ -18,8 +18,10 @@
package io.cloudevents.core.mock;
import io.cloudevents.CloudEvent;
import io.cloudevents.CloudEventData;
import io.cloudevents.SpecVersion;
import io.cloudevents.core.impl.CloudEventUtils;
import io.cloudevents.core.CloudEventUtils;
import io.cloudevents.core.data.BytesCloudEventData;
import io.cloudevents.core.message.MessageReader;
import io.cloudevents.core.message.impl.BaseBinaryMessageReader;
import io.cloudevents.rw.*;
@ -29,83 +31,51 @@ import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
public class MockBinaryMessageWriter extends BaseBinaryMessageReader implements MessageReader, CloudEventWriterFactory<MockBinaryMessageWriter, MockBinaryMessageWriter>, CloudEventWriter<MockBinaryMessageWriter> {
public class MockBinaryMessageWriter extends BaseBinaryMessageReader implements MessageReader, CloudEventContextReader, CloudEventWriterFactory<MockBinaryMessageWriter, MockBinaryMessageWriter>, CloudEventWriter<MockBinaryMessageWriter> {
private SpecVersion version;
private Map<String, Object> attributes;
private byte[] data;
private Map<String, Object> extensions;
private Map<String, Object> context;
private CloudEventData data;
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> attributes, byte[] data, Map<String, Object> extensions) {
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> context, CloudEventData data) {
this.version = version;
this.attributes = attributes;
this.context = context;
this.data = data;
this.extensions = extensions;
}
public MockBinaryMessageWriter(SpecVersion version, Map<String, Object> context, byte[] data) {
this(version, context, BytesCloudEventData.wrap(data));
}
public MockBinaryMessageWriter() {
this.attributes = new HashMap<>();
this.extensions = new HashMap<>();
this.context = new HashMap<>();
}
public MockBinaryMessageWriter(CloudEvent event) {
this();
CloudEventUtils
.toVisitable(event)
.toReader(event)
.read(this);
}
@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 {
if (version == null) {
throw new IllegalStateException("MockBinaryMessage is empty");
}
CloudEventWriter<V> visitor = writerFactory.create(version);
this.readAttributes(visitor);
this.readExtensions(visitor);
CloudEventWriter<V> writer = writerFactory.create(version);
this.readContext(writer);
if (this.data != null && this.data.length != 0) {
return visitor.end(this.data);
if (this.data != null) {
return writer.end(mapper.map(this.data));
}
return visitor.end();
return writer.end();
}
@Override
public void readAttributes(CloudEventAttributesWriter writer) throws CloudEventRWException, IllegalStateException {
for (Map.Entry<String, Object> e : this.attributes.entrySet()) {
if (e.getValue() instanceof String) {
writer.withAttribute(e.getKey(), (String) e.getValue());
} else if (e.getValue() instanceof OffsetDateTime) {
writer.withAttribute(e.getKey(), (OffsetDateTime) e.getValue());
} else if (e.getValue() instanceof URI) {
writer.withAttribute(e.getKey(), (URI) e.getValue());
} else {
// This should never happen because we build that map only through our builders
throw new IllegalStateException("Illegal value inside attributes map: " + e);
}
}
}
@Override
public void readExtensions(CloudEventExtensionsWriter visitor) throws CloudEventRWException, IllegalStateException {
for (Map.Entry<String, Object> entry : this.extensions.entrySet()) {
if (entry.getValue() instanceof String) {
visitor.withExtension(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof Number) {
visitor.withExtension(entry.getKey(), (Number) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
visitor.withExtension(entry.getKey(), (Boolean) entry.getValue());
} else {
// This should never happen because we build that map only through our builders
throw new IllegalStateException("Illegal value inside extensions map: " + entry);
}
}
}
@Override
public MockBinaryMessageWriter end(byte[] value) throws CloudEventRWException {
public MockBinaryMessageWriter end(CloudEventData value) throws CloudEventRWException {
this.data = value;
return this;
}
@ -115,46 +85,60 @@ public class MockBinaryMessageWriter extends BaseBinaryMessageReader implements
return this;
}
@Override
public MockBinaryMessageWriter withAttribute(String name, String value) throws CloudEventRWException {
this.attributes.put(name, value);
return this;
}
@Override
public MockBinaryMessageWriter withAttribute(String name, URI value) throws CloudEventRWException {
this.attributes.put(name, value);
return this;
}
@Override
public MockBinaryMessageWriter withAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
this.attributes.put(name, value);
return this;
}
@Override
public MockBinaryMessageWriter withExtension(String name, String value) throws CloudEventRWException {
this.extensions.put(name, value);
return this;
}
@Override
public MockBinaryMessageWriter withExtension(String name, Number value) throws CloudEventRWException {
this.extensions.put(name, value);
return this;
}
@Override
public MockBinaryMessageWriter withExtension(String name, Boolean value) throws CloudEventRWException {
this.extensions.put(name, value);
return this;
}
@Override
public MockBinaryMessageWriter create(SpecVersion version) {
this.version = version;
return this;
}
@Override
public void readContext(CloudEventContextWriter writer) throws CloudEventRWException {
for (Map.Entry<String, Object> entry : this.context.entrySet()) {
if (entry.getValue() instanceof String) {
writer.withContextAttribute(entry.getKey(), (String) entry.getValue());
} else if (entry.getValue() instanceof OffsetDateTime) {
writer.withContextAttribute(entry.getKey(), (OffsetDateTime) entry.getValue());
} else if (entry.getValue() instanceof URI) {
writer.withContextAttribute(entry.getKey(), (URI) entry.getValue());
} else if (entry.getValue() instanceof Number) {
writer.withContextAttribute(entry.getKey(), (Number) entry.getValue());
} else if (entry.getValue() instanceof Boolean) {
writer.withContextAttribute(entry.getKey(), (Boolean) entry.getValue());
} else {
// This should never happen because we build that map only through our builders
throw new IllegalStateException("Illegal value inside context map: " + entry);
}
}
}
@Override
public CloudEventContextWriter withContextAttribute(String name, String value) throws CloudEventRWException {
this.context.put(name, value);
return this;
}
@Override
public CloudEventContextWriter withContextAttribute(String name, URI value) throws CloudEventRWException {
this.context.put(name, value);
return this;
}
@Override
public CloudEventContextWriter withContextAttribute(String name, OffsetDateTime value) throws CloudEventRWException {
this.context.put(name, value);
return this;
}
@Override
public CloudEventContextWriter withContextAttribute(String name, Number value) throws CloudEventRWException {
this.context.put(name, value);
return this;
}
@Override
public CloudEventContextWriter withContextAttribute(String name, Boolean value) throws CloudEventRWException {
this.context.put(name, value);
return this;
}
}

View File

@ -41,12 +41,12 @@ public class MockStructuredMessageReader extends BaseStructuredMessageReader imp
}
@Override
public <T> T read(StructuredMessageWriter<T> visitor) throws CloudEventRWException, IllegalStateException {
public <T> T read(StructuredMessageWriter<T> writer) throws CloudEventRWException, IllegalStateException {
if (this.format == null) {
throw new IllegalStateException("MockStructuredMessage is empty");
}
return visitor.setEvent(this.format, this.payload);
return writer.setEvent(this.format, this.payload);
}
@Override

View File

@ -0,0 +1,41 @@
package io.cloudevents.core.mock;
import io.cloudevents.CloudEventData;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
public class MyCloudEventData implements CloudEventData {
private final int value;
public MyCloudEventData(int value) {
this.value = value;
}
@Override
public byte[] toBytes() {
return Integer.toString(value).getBytes(StandardCharsets.UTF_8);
}
public int getValue() {
return value;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MyCloudEventData that = (MyCloudEventData) o;
return getValue() == that.getValue();
}
@Override
public int hashCode() {
return Objects.hash(getValue());
}
public static MyCloudEventData fromStringBytes(byte[] bytes) {
return new MyCloudEventData(Integer.valueOf(new String(bytes)));
}
}

View File

@ -36,4 +36,9 @@ public class EventFormatProviderTest {
.isInstanceOf(CSVFormat.class);
}
@Test
void listTypes() {
assertThat(EventFormatProvider.getInstance().getContentTypes()).hasSize(1);
}
}

View File

@ -0,0 +1,17 @@
package io.cloudevents.core.test;
import io.cloudevents.CloudEvent;
import io.cloudevents.core.validator.CloudEventValidator;
public class CloudEventCustomValidator implements CloudEventValidator {
@Override
public void validate(CloudEvent cloudEvent) {
String namespace = null;
if ((namespace = (String) cloudEvent.getExtension("namespace")) != null &&
!namespace.equals("sales")){
throw new IllegalStateException("Expecting sales in namespace extension");
}
}
}

View File

@ -21,7 +21,9 @@ import io.cloudevents.CloudEvent;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.types.Time;
import java.math.BigDecimal;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.time.OffsetDateTime;
import java.util.Objects;
import java.util.stream.Stream;
@ -38,9 +40,10 @@ public class Data {
public static final String SUBJECT = "sub";
public static final OffsetDateTime TIME = Time.parseTime("2018-04-26T14:48:09+02:00");
public static byte[] DATA_JSON_SERIALIZED = "{}".getBytes();
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes();
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes();
public static byte[] DATA_JSON_SERIALIZED = "{}".getBytes(StandardCharsets.UTF_8);
public static byte[] DATA_XML_SERIALIZED = "<stuff></stuff>".getBytes(StandardCharsets.UTF_8);
public static byte[] DATA_TEXT_SERIALIZED = "Hello World Lorena!".getBytes(StandardCharsets.UTF_8);
public static byte[] BINARY_VALUE = { (byte) 0xE0, (byte) 0xFF, (byte) 0x00, (byte) 0x44, (byte) 0xAA }; // Base64: 4P8ARKo=
public static final CloudEvent V1_MIN = CloudEventBuilder.v1()
.withId(ID)
@ -108,6 +111,23 @@ public class Data {
.withTime(TIME)
.build();
public static final CloudEvent V1_WITH_BINARY_EXT = CloudEventBuilder.v1()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.withExtension("binary", BINARY_VALUE)
.build();
public static final CloudEvent V1_WITH_NUMERIC_EXT = CloudEventBuilder.v1()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.withExtension("integer", 42)
.withExtension("decimal", new BigDecimal("42.42"))
.withExtension("float", 4.2f)
.withExtension("long", new Long(4200))
.build();
public static final CloudEvent V03_MIN = CloudEventBuilder.v03(V1_MIN).build();
public static final CloudEvent V03_WITH_JSON_DATA = CloudEventBuilder.v03(V1_WITH_JSON_DATA).build();
public static final CloudEvent V03_WITH_JSON_DATA_WITH_EXT = CloudEventBuilder.v03(V1_WITH_JSON_DATA_WITH_EXT).build();
@ -137,6 +157,18 @@ public class Data {
);
}
/**
* Due to the nature of CE there are scenarios where an event might be serialized
* in such a fashion that it can not be deserialized while retaining the orginal
* type information, this varies from format-2-format
*/
public static Stream<CloudEvent> v1NonRoundTripEvents() {
return Stream.of(
Data.V1_WITH_BINARY_EXT
);
}
public static Stream<CloudEvent> v03Events() {
return Stream.of(
Data.V03_MIN,

View File

@ -73,6 +73,24 @@ public class CloudEventBuilderTest {
assertThat(actual).isEqualTo(expected);
}
@Test
void testCopyExtensions() {
io.cloudevents.core.v03.CloudEventBuilder templateBuilder = CloudEventBuilder.v03()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.withSubject(SUBJECT)
.withTime(TIME)
.withExtension("astring", "aaa")
.withExtension("aboolean", "true")
.withExtension("anumber", "10");
CloudEvent event = templateBuilder.build();
CloudEvent cloned = new io.cloudevents.core.v03.CloudEventBuilder(event).build();
assertThat(cloned).isEqualTo(event);
}
@Test
void testNewBuilder() {
io.cloudevents.core.v03.CloudEventBuilder templateBuilder = CloudEventBuilder.v03()
@ -92,6 +110,24 @@ public class CloudEventBuilderTest {
assertThat(cloned).isEqualTo(event);
}
@Test
void testDataContentEncoding() {
CloudEvent event = CloudEventBuilder.v03()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.withData(DATACONTENTTYPE_JSON, DATASCHEMA, DATA_JSON_SERIALIZED)
.withSubject(SUBJECT)
.withTime(TIME)
.withExtension("astring", "aaa")
.withExtension("aboolean", "true")
.withExtension("anumber", "10")
.build();
assertThat(event.getAttribute("datacontentencoding"))
.isNull();
}
@Test
void testMissingId() {
assertThatCode(() -> CloudEventBuilder
@ -122,4 +158,27 @@ public class CloudEventBuilderTest {
).hasMessageContaining("Attribute 'type' cannot be null");
}
@Test
void testMissingSubject() {
CloudEvent actual = CloudEventBuilder
.v03()
.withId("000")
.withSource(URI.create("http://localhost"))
.withType(TYPE)
.build();
assertThat(actual).isNotNull();
}
@Test
void testEmptySubject() {
assertThatCode(() -> CloudEventBuilder
.v03()
.withId("000")
.withSource(URI.create("http://localhost"))
.withType(TYPE)
.withSubject("")
.build()
).hasMessageContaining("Attribute 'subject' cannot be empty");
}
}

View File

@ -74,6 +74,25 @@ public class CloudEventBuilderTest {
assertThat(actual).isEqualTo(expected);
}
@Test
void testCopyExtensions() {
io.cloudevents.core.v1.CloudEventBuilder templateBuilder = CloudEventBuilder.v1()
.withId(ID)
.withType(TYPE)
.withSource(SOURCE)
.withData(DATACONTENTTYPE_JSON, DATASCHEMA, DATA_JSON_SERIALIZED)
.withSubject(SUBJECT)
.withTime(TIME)
.withExtension("astring", "aaa")
.withExtension("aboolean", "true")
.withExtension("anumber", "10");
CloudEvent event = templateBuilder.build();
CloudEvent cloned = new io.cloudevents.core.v1.CloudEventBuilder(event).build();
assertThat(cloned).isEqualTo(event);
}
@Test
void testNewBuilder() {
io.cloudevents.core.v1.CloudEventBuilder templateBuilder = CloudEventBuilder.v1()
@ -123,4 +142,39 @@ public class CloudEventBuilderTest {
).hasMessageContaining("Attribute 'type' cannot be null");
}
@Test
void testValidatorProvider(){
assertThatCode(() -> CloudEventBuilder
.v1()
.withId("000")
.withSource(URI.create("http://localhost"))
.withType(TYPE)
.withExtension("namespace", "order")
.build()
).hasMessageContaining("Expecting sales in namespace extension");
}
@Test
void testMissingSubject() {
CloudEvent actual = CloudEventBuilder
.v1()
.withId("000")
.withSource(URI.create("http://localhost"))
.withType(TYPE)
.build();
assertThat(actual).isNotNull();
}
@Test
void testEmptySubject() {
assertThatCode(() -> CloudEventBuilder
.v1()
.withId("000")
.withSource(URI.create("http://localhost"))
.withType(TYPE)
.withSubject("")
.build()
).hasMessageContaining("Attribute 'subject' cannot be empty");
}
}

View File

@ -0,0 +1 @@
io.cloudevents.core.test.CloudEventCustomValidator

Some files were not shown because too many files have changed in this diff Show More