diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c12e8fe6a..e6ab01255c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,63 @@ ## [Unreleased](https://github.com/aklivity/zilla/tree/HEAD) -[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.67...HEAD) +[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.68...HEAD) + +**Implemented enhancements:** + +- Support parameters in KafkaTopicsConfig [\#809](https://github.com/aklivity/zilla/pull/809) ([bmaidics](https://github.com/bmaidics)) + +**Fixed bugs:** + +- SEVERE: Problem adapting object of type class NamespaceConfig to interface jakarta.json.JsonObject in class class NamespaceAdapter [\#796](https://github.com/aklivity/zilla/issues/796) +- Zilla is validating `env` vars before replacing them. [\#795](https://github.com/aklivity/zilla/issues/795) +- Basic Docker Compose Setup Clogs CPU With Error Messages [\#722](https://github.com/aklivity/zilla/issues/722) + +**Closed issues:** + +- Use dedicated env var to enable Incubator features [\#800](https://github.com/aklivity/zilla/issues/800) +- Support `http` to `kafka` proxy using `openapi.yaml` and `asyncapi.yaml` [\#742](https://github.com/aklivity/zilla/issues/742) +- Support `mqtt` to `kafka` proxy using `asyncapi.yaml` [\#741](https://github.com/aklivity/zilla/issues/741) +- Support `openapi` `http` proxy using `openapi.yaml` [\#740](https://github.com/aklivity/zilla/issues/740) +- Support `asyncapi` `http` proxy using `asyncapi.yaml` [\#739](https://github.com/aklivity/zilla/issues/739) +- Support `asyncapi` `mqtt` proxy using `asyncapi.yaml` [\#738](https://github.com/aklivity/zilla/issues/738) +- Support local logging of events caused by external actors [\#679](https://github.com/aklivity/zilla/issues/679) + +**Merged pull requests:** + +- Asyncapi and Openapi bug fixes [\#826](https://github.com/aklivity/zilla/pull/826) ([akrambek](https://github.com/akrambek)) +- Asyncapi catalog implementation [\#825](https://github.com/aklivity/zilla/pull/825) ([bmaidics](https://github.com/bmaidics)) +- Fix NPE in KafkaSignalStream [\#823](https://github.com/aklivity/zilla/pull/823) ([bmaidics](https://github.com/bmaidics)) +- Fix early flush sending for retained stream [\#822](https://github.com/aklivity/zilla/pull/822) ([bmaidics](https://github.com/bmaidics)) +- Add incubating annotation for stdout exporter [\#819](https://github.com/aklivity/zilla/pull/819) ([jfallows](https://github.com/jfallows)) +- MQTT-Kafka asyncapi proxy [\#818](https://github.com/aklivity/zilla/pull/818) ([bmaidics](https://github.com/bmaidics)) +- Fix kafka client composite resolvedId [\#816](https://github.com/aklivity/zilla/pull/816) ([bmaidics](https://github.com/bmaidics)) +- Use env var to add incubator java option [\#811](https://github.com/aklivity/zilla/pull/811) ([vordimous](https://github.com/vordimous)) +- Support http to kafka proxy using openapi.yaml and asyncapi.yaml [\#810](https://github.com/aklivity/zilla/pull/810) ([akrambek](https://github.com/akrambek)) +- Structured models require `catalog` config [\#807](https://github.com/aklivity/zilla/pull/807) ([aDaemonThread](https://github.com/aDaemonThread)) +- Include qualified vault name on binding [\#806](https://github.com/aklivity/zilla/pull/806) ([jfallows](https://github.com/jfallows)) +- Include config exception cause [\#805](https://github.com/aklivity/zilla/pull/805) ([jfallows](https://github.com/jfallows)) +- Kafka asyncapi client [\#804](https://github.com/aklivity/zilla/pull/804) ([bmaidics](https://github.com/bmaidics)) +- Support k3po ephemeral option [\#801](https://github.com/aklivity/zilla/pull/801) ([akrambek](https://github.com/akrambek)) +- Support asyncapi http proxy using asyncapi.yaml [\#799](https://github.com/aklivity/zilla/pull/799) ([bmaidics](https://github.com/bmaidics)) +- Fix kafka sasl schema validation to support expressions [\#798](https://github.com/aklivity/zilla/pull/798) ([akrambek](https://github.com/akrambek)) +- Zilla is validating env vars before replacing them [\#797](https://github.com/aklivity/zilla/pull/797) ([akrambek](https://github.com/akrambek)) +- Support openapi http proxy using openapi.yaml [\#778](https://github.com/aklivity/zilla/pull/778) ([akrambek](https://github.com/akrambek)) +- Support asyncapi mqtt proxy using asyncapi.yaml [\#764](https://github.com/aklivity/zilla/pull/764) ([bmaidics](https://github.com/bmaidics)) +- Support local logging of events [\#755](https://github.com/aklivity/zilla/pull/755) ([attilakreiner](https://github.com/attilakreiner)) + +## [0.9.68](https://github.com/aklivity/zilla/tree/0.9.68) (2024-02-13) + +[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.67...0.9.68) **Fixed bugs:** - Zilla crashes when a large number of MQTT clients connect [\#793](https://github.com/aklivity/zilla/issues/793) +**Merged pull requests:** + +- Require group host and port for `kafka` coordinator-specific streams [\#794](https://github.com/aklivity/zilla/pull/794) ([jfallows](https://github.com/jfallows)) + ## [0.9.67](https://github.com/aklivity/zilla/tree/0.9.67) (2024-02-11) [Full Changelog](https://github.com/aklivity/zilla/compare/0.9.66...0.9.67) @@ -15,10 +66,6 @@ **Implemented enhancements:** - Use `model` and `view` when describing the message type [\#750](https://github.com/aklivity/zilla/issues/750) -- Support obtaining `protobuf` schemas from `schema registry` for `grpc` services [\#697](https://github.com/aklivity/zilla/issues/697) -- Support idempotent `mqtt` `qos 2` publish to `kafka` [\#677](https://github.com/aklivity/zilla/issues/677) -- Detect and inspect invalid messages received [\#676](https://github.com/aklivity/zilla/issues/676) -- Support incremental validation of fragmented messages sent by client [\#671](https://github.com/aklivity/zilla/issues/671) - Catalog cache TTL implementation [\#658](https://github.com/aklivity/zilla/pull/658) ([aDaemonThread](https://github.com/aDaemonThread)) **Fixed bugs:** @@ -31,6 +78,13 @@ - Fix zilla crash when it tries to send flush on retain stream [\#784](https://github.com/aklivity/zilla/pull/784) ([bmaidics](https://github.com/bmaidics)) - Limit sharding to mqtt 5 [\#760](https://github.com/aklivity/zilla/pull/760) ([bmaidics](https://github.com/bmaidics)) +**Closed issues:** + +- Support obtaining `protobuf` schemas from `schema registry` for `grpc` services [\#697](https://github.com/aklivity/zilla/issues/697) +- Support idempotent `mqtt` `qos 2` publish to `kafka` [\#677](https://github.com/aklivity/zilla/issues/677) +- Detect and inspect invalid messages received [\#676](https://github.com/aklivity/zilla/issues/676) +- Support incremental validation of fragmented messages sent by client [\#671](https://github.com/aklivity/zilla/issues/671) + **Merged pull requests:** - Simplify TLSv1.3 handshake check [\#792](https://github.com/aklivity/zilla/pull/792) ([jfallows](https://github.com/jfallows)) @@ -65,16 +119,16 @@ [Full Changelog](https://github.com/aklivity/zilla/compare/0.9.65...0.9.66) -**Implemented enhancements:** +**Fixed bugs:** + +- Schema validation fails before the `${{env.*}}` parameters have been removed [\#583](https://github.com/aklivity/zilla/issues/583) + +**Closed issues:** - Support `openapi` `http` response validation [\#684](https://github.com/aklivity/zilla/issues/684) - Support `protobuf` conversion to and from `json` for `kafka` messages [\#682](https://github.com/aklivity/zilla/issues/682) - Support incubator features preview in zilla release docker image [\#670](https://github.com/aklivity/zilla/issues/670) -**Fixed bugs:** - -- Schema validation fails before the `${{env.*}}` parameters have been removed [\#583](https://github.com/aklivity/zilla/issues/583) - **Merged pull requests:** - update license exclude path to include both zpmw files [\#759](https://github.com/aklivity/zilla/pull/759) ([vordimous](https://github.com/vordimous)) @@ -90,10 +144,6 @@ **Implemented enhancements:** -- Support `avro` conversion to and from `json` for `kafka` messages [\#681](https://github.com/aklivity/zilla/issues/681) -- Support observability of zilla engine internal streams [\#678](https://github.com/aklivity/zilla/issues/678) -- Simplify configuration of multiple protocols on different tcp ports [\#669](https://github.com/aklivity/zilla/issues/669) -- Simplify kafka client bootstrap server names and ports config [\#619](https://github.com/aklivity/zilla/issues/619) - MQTT publish QoS 2 as Kafka produce with acks in\_sync\_replicas and idempotent `producerId` [\#605](https://github.com/aklivity/zilla/issues/605) - Add the option to route by `port` in the `tls` binding [\#564](https://github.com/aklivity/zilla/issues/564) - Support outbound message transformation from `protobuf` to `json` [\#458](https://github.com/aklivity/zilla/issues/458) @@ -124,6 +174,10 @@ **Closed issues:** - Prototype composite binding support with nested namespaces [\#685](https://github.com/aklivity/zilla/issues/685) +- Support `avro` conversion to and from `json` for `kafka` messages [\#681](https://github.com/aklivity/zilla/issues/681) +- Support observability of zilla engine internal streams [\#678](https://github.com/aklivity/zilla/issues/678) +- Simplify configuration of multiple protocols on different tcp ports [\#669](https://github.com/aklivity/zilla/issues/669) +- Simplify kafka client bootstrap server names and ports config [\#619](https://github.com/aklivity/zilla/issues/619) - Build has been failed in local [\#229](https://github.com/aklivity/zilla/issues/229) **Merged pull requests:** diff --git a/build/flyweight-maven-plugin/pom.xml b/build/flyweight-maven-plugin/pom.xml index f87405e06b..fad482be76 100644 --- a/build/flyweight-maven-plugin/pom.xml +++ b/build/flyweight-maven-plugin/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla build - 0.9.68 + 0.9.69 ../pom.xml diff --git a/build/pom.xml b/build/pom.xml index 26db82e124..9287417e6b 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml index 320d2634ef..801505b541 100644 --- a/cloud/docker-image/pom.xml +++ b/cloud/docker-image/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla cloud - 0.9.68 + 0.9.69 ../pom.xml @@ -43,6 +43,24 @@ ${project.version} runtime + + ${project.groupId} + binding-asyncapi + ${project.version} + runtime + + + ${project.groupId} + binding-openapi + ${project.version} + runtime + + + ${project.groupId} + binding-openapi-asyncapi + ${project.version} + runtime + ${project.groupId} binding-echo @@ -223,6 +241,12 @@ ${project.version} runtime + + ${project.groupId} + exporter-stdout + ${project.version} + runtime + ${project.groupId} metrics-stream diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template index 427e9b3f99..790c062053 100644 --- a/cloud/docker-image/src/main/docker/zpm.json.template +++ b/cloud/docker-image/src/main/docker/zpm.json.template @@ -14,6 +14,7 @@ "dependencies": [ "io.aklivity.zilla:binding-amqp", + "io.aklivity.zilla:binding-asyncapi", "io.aklivity.zilla:binding-echo", "io.aklivity.zilla:binding-fan", "io.aklivity.zilla:binding-filesystem", @@ -26,6 +27,8 @@ "io.aklivity.zilla:binding-kafka", "io.aklivity.zilla:binding-mqtt", "io.aklivity.zilla:binding-mqtt-kafka", + "io.aklivity.zilla:binding-openapi", + "io.aklivity.zilla:binding-openapi-asyncapi", "io.aklivity.zilla:binding-proxy", "io.aklivity.zilla:binding-sse", "io.aklivity.zilla:binding-sse-kafka", @@ -43,8 +46,9 @@ "io.aklivity.zilla:command-stop", "io.aklivity.zilla:command-tune", "io.aklivity.zilla:engine", - "io.aklivity.zilla:exporter-prometheus", "io.aklivity.zilla:exporter-otlp", + "io.aklivity.zilla:exporter-prometheus", + "io.aklivity.zilla:exporter-stdout", "io.aklivity.zilla:guard-jwt", "io.aklivity.zilla:metrics-stream", "io.aklivity.zilla:metrics-http", diff --git a/cloud/helm-chart/pom.xml b/cloud/helm-chart/pom.xml index 93117c23e6..19fe35c8f9 100644 --- a/cloud/helm-chart/pom.xml +++ b/cloud/helm-chart/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla cloud - 0.9.68 + 0.9.69 ../pom.xml diff --git a/cloud/pom.xml b/cloud/pom.xml index e84cfbd44c..be5d7b7ede 100644 --- a/cloud/pom.xml +++ b/cloud/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml diff --git a/conf/pom.xml b/conf/pom.xml index 1b22c2510b..a4e6729542 100644 --- a/conf/pom.xml +++ b/conf/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/binding-amqp.spec/pom.xml b/incubator/binding-amqp.spec/pom.xml index a68b6467df..f502c6e2ef 100644 --- a/incubator/binding-amqp.spec/pom.xml +++ b/incubator/binding-amqp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/binding-amqp/pom.xml b/incubator/binding-amqp/pom.xml index 6133d94f01..7841077e88 100644 --- a/incubator/binding-amqp/pom.xml +++ b/incubator/binding-amqp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/binding-asyncapi.spec/COPYRIGHT b/incubator/binding-asyncapi.spec/COPYRIGHT new file mode 100644 index 0000000000..8b1b7215ef --- /dev/null +++ b/incubator/binding-asyncapi.spec/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright ${copyrightYears} Aklivity Inc. + +Aklivity licenses this file to you 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: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. diff --git a/incubator/binding-asyncapi.spec/LICENSE b/incubator/binding-asyncapi.spec/LICENSE new file mode 100644 index 0000000000..8dada3edaf --- /dev/null +++ b/incubator/binding-asyncapi.spec/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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 + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/incubator/binding-asyncapi.spec/NOTICE b/incubator/binding-asyncapi.spec/NOTICE new file mode 100644 index 0000000000..8ac96c59e7 --- /dev/null +++ b/incubator/binding-asyncapi.spec/NOTICE @@ -0,0 +1,28 @@ +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: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-kafka.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-mqtt-kafka.spec under Aklivity Community License Agreement + zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-asyncapi.spec/NOTICE.template b/incubator/binding-asyncapi.spec/NOTICE.template new file mode 100644 index 0000000000..e9ed8f0e7b --- /dev/null +++ b/incubator/binding-asyncapi.spec/NOTICE.template @@ -0,0 +1,18 @@ +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: + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, +software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, either express or implied. See the License for the +specific language governing permissions and limitations +under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ \ No newline at end of file diff --git a/incubator/binding-asyncapi.spec/mvnw b/incubator/binding-asyncapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-asyncapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-asyncapi.spec/mvnw.cmd b/incubator/binding-asyncapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-asyncapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-asyncapi.spec/pom.xml b/incubator/binding-asyncapi.spec/pom.xml new file mode 100644 index 0000000000..1a20bb45c8 --- /dev/null +++ b/incubator/binding-asyncapi.spec/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + binding-asyncapi.spec + zilla::incubator::binding-asyncapi.spec + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.91 + 0 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-mqtt.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + + + ${project.groupId} + binding-kafka.spec + ${project.version} + + + ${project.groupId} + binding-mqtt-kafka.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http mqtt kafka asyncapi + io.aklivity.zilla.specs.binding.asyncapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/asyncapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java new file mode 100644 index 0000000000..440d91d71c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctions.java @@ -0,0 +1,206 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi; + +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.kaazing.k3po.lang.el.BytesMatcher; +import org.kaazing.k3po.lang.el.Function; +import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; + +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; + +public final class AsyncapiFunctions +{ + @Function + public static AsyncapiBeginExBuilder beginEx() + { + return new AsyncapiBeginExBuilder(); + } + + @Function + public static AsyncapiBeginExMatcherBuilder matchBeginEx() + { + return new AsyncapiBeginExMatcherBuilder(); + } + + public static final class AsyncapiBeginExBuilder + { + private final AsyncapiBeginExFW.Builder beginExRW; + + private AsyncapiBeginExBuilder() + { + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1024 * 8]); + this.beginExRW = new AsyncapiBeginExFW.Builder().wrap(writeBuffer, 0, writeBuffer.capacity()); + } + + public AsyncapiBeginExBuilder typeId( + int typeId) + { + beginExRW.typeId(typeId); + return this; + } + + public AsyncapiBeginExBuilder apiId( + long apiId) + { + beginExRW.apiId(apiId); + return this; + } + + public AsyncapiBeginExBuilder operationId( + String operationId) + { + beginExRW.operationId(operationId); + return this; + } + + public AsyncapiBeginExBuilder extension( + byte[] extension) + { + beginExRW.extension(e -> e.set(extension)); + return this; + } + + public byte[] build() + { + final AsyncapiBeginExFW beginEx = beginExRW.build(); + final byte[] array = new byte[beginEx.sizeof()]; + beginEx.buffer().getBytes(beginEx.offset(), array); + return array; + } + } + + public static final class AsyncapiBeginExMatcherBuilder + { + private final DirectBuffer bufferRO = new UnsafeBuffer(); + + private final AsyncapiBeginExFW beginExRO = new AsyncapiBeginExFW(); + + private Integer typeId; + private long apiId; + private String operationId; + private OctetsFW.Builder extensionRW; + + public AsyncapiBeginExMatcherBuilder typeId( + int typeId) + { + this.typeId = typeId; + return this; + } + + public AsyncapiBeginExMatcherBuilder operationId( + String operationId) + { + this.operationId = operationId; + return this; + } + + public AsyncapiBeginExMatcherBuilder apiId( + long apiId) + { + this.apiId = apiId; + return this; + } + + public AsyncapiBeginExMatcherBuilder extension( + byte[] extension) + { + assert extensionRW == null; + extensionRW = new OctetsFW.Builder().wrap(new UnsafeBuffer(new byte[1024]), 0, 1024); + + extensionRW.set(Objects.requireNonNullElseGet(extension, () -> new byte[] {})); + + return this; + } + + public BytesMatcher build() + { + return typeId != null ? this::match : buf -> null; + } + + private AsyncapiBeginExFW match( + ByteBuffer byteBuf) throws Exception + { + if (!byteBuf.hasRemaining()) + { + return null; + } + + bufferRO.wrap(byteBuf); + final AsyncapiBeginExFW beginEx = beginExRO.tryWrap(bufferRO, byteBuf.position(), byteBuf.limit()); + + if (beginEx != null && + matchTypeId(beginEx) && + matchApiId(beginEx) && + matchOperationId(beginEx) && + matchExtension(beginEx)) + { + byteBuf.position(byteBuf.position() + beginEx.sizeof()); + return beginEx; + } + throw new Exception(beginEx.toString()); + } + + private boolean matchTypeId( + AsyncapiBeginExFW beginEx) + { + return typeId == beginEx.typeId(); + } + + private boolean matchApiId( + AsyncapiBeginExFW beginEx) + { + return apiId == beginEx.apiId(); + } + + private boolean matchOperationId( + AsyncapiBeginExFW beginEx) + { + return operationId == null || operationId.equals(beginEx.operationId().asString()); + } + + private boolean matchExtension( + final AsyncapiBeginExFW beginEx) + { + return extensionRW == null || extensionRW.build().equals(beginEx.extension()); + } + } + + private AsyncapiFunctions() + { + // utility + } + + public static class Mapper extends FunctionMapperSpi.Reflective + { + public Mapper() + { + super(AsyncapiFunctions.class); + } + + @Override + public String getPrefixName() + { + return "asyncapi"; + } + } +} diff --git a/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java new file mode 100644 index 0000000000..262fc5de9c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiSpecs.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi; + +public final class AsyncapiSpecs +{ + private AsyncapiSpecs() + { + } +} diff --git a/incubator/binding-asyncapi.spec/src/main/moditect/module-info.java b/incubator/binding-asyncapi.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..187284829c --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/moditect/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +open module io.aklivity.zilla.specs.binding.asyncapi +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..194c889324 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1 @@ +io.aklivity.zilla.specs.binding.asyncapi.AsyncapiFunctions$Mapper diff --git a/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl new file mode 100644 index 0000000000..22dc7b0946 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/resources/META-INF/zilla/asyncapi.idl @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope asyncapi +{ + scope stream + { + struct AsyncapiBeginEx extends core::stream::Extension + { + int64 apiId = 0; + string16 operationId = null; + octets extension; + } + } +} diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml new file mode 100644 index 0000000000..177a2ed8ed --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.secure.yaml @@ -0,0 +1,41 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + asyncapi0: + type: asyncapi + kind: client + vault: test0 + options: + specs: + http_api: http/asyncapi.yaml + tcp: + host: localhost + port: + - 7080 + tls: + trust: + - serverca + trustcacerts: true + sni: + - http.example.net + alpn: + - h2 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml new file mode 100644 index 0000000000..246b0e5dfd --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.http.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi0: + type: asyncapi + kind: client + options: + specs: + http_api: http/asyncapi.yaml + tcp: + host: localhost + port: + - 7080 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml new file mode 100644 index 0000000000..ded9bd7624 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.sasl.yaml @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + asyncapi0: + type: asyncapi + kind: client + vault: test0 + options: + specs: + kafka_api: kafka/asyncapi.yaml + tcp: + host: localhost + port: + - 9092 + tls: + trust: + - serverca + trustcacerts: true + sni: + - kafka.example.net + alpn: + - kafka + kafka: + sasl: + mechanism: plain + username: username + password: password diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml new file mode 100644 index 0000000000..8719d56e79 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.kafka.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi0: + type: asyncapi + kind: client + options: + specs: + kafka_api: kafka/asyncapi.yaml + tcp: + host: localhost + port: + - 9092 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml new file mode 100644 index 0000000000..ed373d7a29 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.secure.yaml @@ -0,0 +1,41 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + asyncapi0: + type: asyncapi + kind: client + vault: test0 + options: + specs: + mqtt_api: mqtt/asyncapi.yaml + tcp: + host: localhost + port: + - 7183 + tls: + trust: + - serverca + trustcacerts: true + sni: + - mqtt.example.net + alpn: + - mqtt diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml new file mode 100644 index 0000000000..65f9033370 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/client.mqtt.yaml @@ -0,0 +1,29 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi0: + type: asyncapi + kind: client + options: + specs: + mqtt_api: mqtt/asyncapi.yaml + tcp: + host: localhost + port: + - 7183 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml new file mode 100644 index 0000000000..5c4b7a0dbe --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/http/asyncapi.yaml @@ -0,0 +1,96 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: AsyncAPI Petstore + license: + name: MIT + version: 1.0.0 +servers: + plain: + host: http://localhost:8080 + protocol: http + protocolVersion: '2.0' +defaultContentType: application/json + +channels: + pets: + address: /pets + messages: + pet: + $ref: '#/components/messages/pet' + showPetById: + address: /pets/{id} + messages: + pet: + $ref: '#/components/messages/pet' + +operations: + createPet: + action: send + bindings: + http: + method: POST + channel: + $ref: '#/channels/pets' + listPets: + action: receive + bindings: + http: + method: GET + channel: + $ref: '#/channels/pets' + getPets: + action: receive + bindings: + http: + method: GET + query: + type: object + properties: + limit: + type: number + channel: + $ref: '#/channels/showPetById' + +components: + correlationIds: + petsCorrelationId: + location: '$message.header#/idempotency-key' + schemas: + petPayload: + type: object + properties: + id: + type: integer + minimum: 0 + description: Pet id. + name: + type: string + description: Pet name. + tag: + type: string + description: Tag. + messages: + pet: + name: Pet + title: Pet + summary: >- + Inform about Pet. + contentType: application/json + payload: + $ref: '#/components/schemas/petPayload' diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml new file mode 100644 index 0000000000..1c7b1bf0df --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/asyncapi.yaml @@ -0,0 +1,199 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: Streetlights Kafka API + version: 1.0.0 + description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n" + license: + name: Apache 2.0 + url: 'https://www.apache.org/licenses/LICENSE-2.0' +defaultContentType: application/json +servers: + scram-connections: + host: 'test.mykafkacluster.org:18092' + protocol: kafka-secure + description: Test broker secured with scramSha256 + security: + - $ref: '#/components/securitySchemes/saslScram' + tags: + - name: 'env:test-scram' + description: >- + This environment is meant for running internal tests through + scramSha256 + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application + - name: 'visibility:private' + description: This resource is private and only available to certain users +channels: + lightingMeasured: + address: 'smartylighting.streetlights.1.0.event.{streetlightId}.lighting.measured' + messages: + lightMeasured: + $ref: '#/components/messages/lightMeasured' + description: The topic on which measured values may be produced and consumed. + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightTurnOn: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.on' + messages: + turnOn: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightTurnOff: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.turn.off' + messages: + turnOff: + $ref: '#/components/messages/turnOnOff' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' + lightsDim: + address: 'smartylighting.streetlights.1.0.action.{streetlightId}.dim' + messages: + dimLight: + $ref: '#/components/messages/dimLight' + parameters: + streetlightId: + $ref: '#/components/parameters/streetlightId' +operations: + receiveLightMeasurement: + action: receive + channel: + $ref: '#/channels/lightingMeasured' + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightingMeasured/messages/lightMeasured' + turnOn: + action: send + channel: + $ref: '#/channels/lightTurnOn' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightTurnOn/messages/turnOn' + turnOff: + action: send + channel: + $ref: '#/channels/lightTurnOff' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightTurnOff/messages/turnOff' + dimLight: + action: send + channel: + $ref: '#/channels/lightsDim' + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/lightsDim/messages/dimLight' +components: + messages: + lightMeasured: + name: lightMeasured + title: Light measured + summary: >- + Inform about environmental lighting conditions of a particular + streetlight. + contentType: application/json + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/lightMeasuredPayload' + turnOnOff: + name: turnOnOff + title: Turn on/off + summary: Command a particular streetlight to turn the lights on or off. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/turnOnOffPayload' + dimLight: + name: dimLight + title: Dim light + summary: Command a particular streetlight to dim the lights. + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/dimLightPayload' + schemas: + lightMeasuredPayload: + type: object + properties: + lumens: + type: integer + minimum: 0 + description: Light intensity measured in lumens. + sentAt: + $ref: '#/components/schemas/sentAt' + turnOnOffPayload: + type: object + properties: + command: + type: string + enum: + - 'on' + - 'off' + description: Whether to turn on or off the light. + sentAt: + $ref: '#/components/schemas/sentAt' + dimLightPayload: + type: object + properties: + percentage: + type: integer + description: Percentage to which the light should be dimmed to. + minimum: 0 + maximum: 100 + sentAt: + $ref: '#/components/schemas/sentAt' + sentAt: + type: string + format: date-time + description: Date and time when the message was sent. + securitySchemes: + saslScram: + type: scramSha256 + description: Provide your username and password for SASL/SCRAM authentication + parameters: + streetlightId: + description: The ID of the streetlight. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + kafka: + bindings: + kafka: + clientId: + type: string + enum: + - my-app-id diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml new file mode 100644 index 0000000000..020633da3f --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/kafka/sensor.asyncapi.yaml @@ -0,0 +1,66 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: Zilla Kafka Proxy + version: 1.0.0 + license: + name: Aklivity Community License +servers: + plain: + host: localhost:9092 + protocol: kafka + +operations: + onSensorData: + action: receive + channel: + $ref: '#/channels/sensorData' + toSensorData: + action: send + channel: + $ref: '#/channels/sensorData' + +channels: + sensorData: + description: This channel contains a message for sensors. + address: sensors + messages: + sensorData: + $ref: '#/components/messages/sensorData' + mqttSessions: + description: This channel contains MQTT sessions. + address: mqtt-sessions + mqttMessages: + description: This channel contains MQTT messages. + address: mqtt-messages + mqttRetained: + description: This channel contains MQTT retained messages. + address: mqtt-retained + +components: + messages: + sensorData: + payload: + type: object + properties: + sensorId: + type: integer + description: This property describes the id of the sensor + message: + type: string + description: This property describes message of the sensor diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml new file mode 100644 index 0000000000..7aa9fca2e5 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/mqtt/asyncapi.yaml @@ -0,0 +1,68 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +asyncapi: 3.0.0 +info: + title: Zilla MQTT Proxy + version: 1.0.0 + license: + name: Aklivity Community License +servers: + plain: + host: mqtt://localhost:1883 + protocol: mqtt +defaultContentType: application/json + +channels: + sensors: + address: "sensors/{sensorId}" + title: MQTT Topic to produce & consume topic. + parameters: + streetlightId: + $ref: '#/components/parameters/sensorId' + messages: + items: + $ref: '#/components/messages/item' + +operations: + sendEvents: + action: send + channel: + $ref: '#/channels/sensors' + + receiveEvents: + action: receive + channel: + $ref: '#/channels/sensors' + +components: + parameters: + sensorId: + description: Sensor ID + location: $message.header#/id + messages: + item: + name: event + title: An event + headers: + type: object + properties: + idempotency-key: + description: Unique identifier for a given event + type: string + id: + description: Sensor ID + type: string diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml new file mode 100644 index 0000000000..deb7634692 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/proxy.mqtt.kafka.yaml @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + asyncapi_proxy0: + type: asyncapi + kind: proxy + options: + specs: + mqtt_api: mqtt/asyncapi.yaml + kafka_api: kafka/sensor.asyncapi.yaml + mqtt_kafka: + channels: + sessions: mqttSessions + retained: mqttRetained + messages: mqttMessages + routes: + - when: + - api-id: mqtt_api + operation-id: sendEvents + exit: asyncapi_kafka0 + with: + api-id: kafka_api + operation-id: toSensorData + - when: + - api-id: mqtt_api + operation-id: receiveEvents + exit: asyncapi_kafka0 + with: + api-id: kafka_api + operation-id: onSensorData diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml new file mode 100644 index 0000000000..a09b6aefd6 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.secure.yaml @@ -0,0 +1,37 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + composite0: + type: asyncapi + kind: server + vault: test0 + options: + specs: + http_api: http/asyncapi.yaml + tls: + keys: + - localhost + sni: + - http.example.net + alpn: + - h2 + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml new file mode 100644 index 0000000000..1bb68c32d3 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.http.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: asyncapi + kind: server + options: + specs: + http_api: http/asyncapi.yaml + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml new file mode 100644 index 0000000000..80fee3c5dd --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.secure.yaml @@ -0,0 +1,37 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + test0: + type: test +bindings: + composite0: + type: asyncapi + kind: server + vault: test0 + options: + specs: + mqtt_api: mqtt/asyncapi.yaml + tls: + keys: + - localhost + sni: + - mqtt.example.net + alpn: + - mqtt + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml new file mode 100644 index 0000000000..36e41f3429 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/config/server.mqtt.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: asyncapi + kind: server + options: + specs: + mqtt_api: mqtt/asyncapi.yaml + exit: asyncapi0 diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json new file mode 100644 index 0000000000..053cc08f14 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.2.6.schema.json @@ -0,0 +1,3229 @@ +{ + "$id": "http://asyncapi.com/definitions/2.6.0/asyncapi.json", + "$schema": "http://json-schema.org/draft-07/schema", + "title": "AsyncAPI 2.6.0 schema.", + "type": "object", + "required": [ + "asyncapi", + "info", + "channels" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "asyncapi": { + "type": "string", + "enum": [ + "2.6.0" + ], + "description": "The AsyncAPI specification version of this document." + }, + "id": { + "type": "string", + "description": "A unique id representing the application.", + "format": "uri" + }, + "info": { + "$ref": "http://asyncapi.com/definitions/2.6.0/info.json" + }, + "servers": { + "$ref": "http://asyncapi.com/definitions/2.6.0/servers.json" + }, + "defaultContentType": { + "type": "string" + }, + "channels": { + "$ref": "http://asyncapi.com/definitions/2.6.0/channels.json" + }, + "components": { + "$ref": "http://asyncapi.com/definitions/2.6.0/components.json" + }, + "tags": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + } + }, + "definitions": { + "http://asyncapi.com/definitions/2.6.0/specificationExtension.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json", + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "http://asyncapi.com/definitions/2.6.0/info.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/info.json", + "type": "object", + "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/2.6.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/2.6.0/license.json" + } + }, + "examples": [ + { + "title": "AsyncAPI Sample App", + "description": "This is a sample server.", + "termsOfService": "https://asyncapi.org/terms/", + "contact": { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/contact.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/contact.json", + "type": "object", + "description": "Contact information for the exposed API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/license.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/license.json", + "type": "object", + "required": [ + "name" + ], + "description": "License information for the exposed API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/servers.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/servers.json", + "description": "The Servers Object is a map of Server Objects.", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/server.json" + } + ] + }, + "examples": [ + { + "development": { + "url": "development.gigantic-server.com", + "description": "Development server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "tags": [ + { + "name": "env:development", + "description": "This environment is meant for developers to run their own tests" + } + ] + }, + "staging": { + "url": "staging.gigantic-server.com", + "description": "Staging server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "tags": [ + { + "name": "env:staging", + "description": "This environment is a replica of the production environment" + } + ] + }, + "production": { + "url": "api.gigantic-server.com", + "description": "Production server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "tags": [ + { + "name": "env:production", + "description": "This environment is the live environment available for final users" + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/Reference.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/Reference.json", + "type": "object", + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "$ref": "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json" + } + } + }, + "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json", + "type": "string", + "description": "A simple object to allow referencing other components in the specification, internally and externally.", + "format": "uri-reference", + "examples": [ + { + "$ref": "#/components/schemas/Pet" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/server.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/server.json", + "type": "object", + "description": "An object representing a message broker, a server or any other kind of computer program capable of sending and/or receiving data", + "required": [ + "url", + "protocol" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "url": { + "type": "string", + "description": "A URL to the target host. This URL supports Server Variables and MAY be relative, to indicate that the host location is relative to the location where the AsyncAPI document is being served." + }, + "description": { + "type": "string", + "description": "An optional string describing the host designated by the URL. CommonMark syntax MAY be used for rich text representation." + }, + "protocol": { + "type": "string", + "description": "The protocol this URL supports for connection. Supported protocol include, but are not limited to: amqp, amqps, http, https, ibmmq, jms, kafka, kafka-secure, anypointmq, mqtt, secure-mqtt, solace, stomp, stomps, ws, wss, mercure, googlepubsub." + }, + "protocolVersion": { + "type": "string", + "description": "The version of the protocol used for connection. For instance: AMQP 0.9.1, HTTP 2.0, Kafka 1.0.0, etc." + }, + "variables": { + "description": "A map between a variable name and its value. The value is used for substitution in the server's URL template.", + "$ref": "http://asyncapi.com/definitions/2.6.0/serverVariables.json" + }, + "security": { + "type": "array", + "description": "A declaration of which security mechanisms can be used with this server. The list of values includes alternative security requirement objects that can be used. ", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json" + } + }, + "bindings": { + "description": "A map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the server.", + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of servers.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + } + }, + "examples": [ + { + "url": "development.gigantic-server.com", + "description": "Development server", + "protocol": "kafka", + "protocolVersion": "1.0.0" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/serverVariables.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/serverVariables.json", + "type": "object", + "description": "A map between a variable name and its value. The value is used for substitution in the server's URL template.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/serverVariable.json" + } + ] + } + }, + "http://asyncapi.com/definitions/2.6.0/serverVariable.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/serverVariable.json", + "type": "object", + "description": "An object representing a Server Variable for server URL template substitution.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "enum": { + "type": "array", + "description": "An enumeration of string values to be used if the substitution options are from a limited set.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "default": { + "type": "string", + "description": "The default value to use for substitution, and to send, if an alternate value is not supplied." + }, + "description": { + "type": "string", + "description": "An optional description for the server variable. " + }, + "examples": { + "type": "array", + "description": "An array of examples of the server variable.", + "items": { + "type": "string" + } + } + } + }, + "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json", + "type": "object", + "description": "Lists of the required security schemes that can be used to execute an operation", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "examples": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/bindingsObject.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a server.", + "additionalProperties": true, + "properties": { + "http": {}, + "ws": {}, + "amqp": {}, + "amqp1": {}, + "mqtt": {}, + "mqtt5": {}, + "kafka": {}, + "anypointmq": {}, + "nats": {}, + "jms": {}, + "sns": {}, + "sqs": {}, + "stomp": {}, + "redis": {}, + "ibmmq": {}, + "solace": {}, + "googlepubsub": {}, + "pulsar": {} + } + }, + "http://asyncapi.com/definitions/2.6.0/tag.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/tag.json", + "type": "object", + "description": "Allows adding meta data to a single tag.", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the tag." + }, + "description": { + "type": "string", + "description": "A short description for the tag." + }, + "externalDocs": { + "description": "Additional external documentation for this tag.", + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "user", + "description": "User-related messages" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/externalDocs.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/externalDocs.json", + "type": "object", + "additionalProperties": false, + "description": "Allows referencing an external resource for extended documentation.", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string", + "description": "A short description of the target documentation." + }, + "url": { + "type": "string", + "format": "uri", + "description": "The URL for the target documentation. This MUST be in the form of an absolute URL." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "examples": [ + { + "description": "Find more info here", + "url": "https://example.com" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/channels.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/channels.json", + "type": "object", + "description": "Holds the relative paths to the individual channel and their operations. Channel paths are relative to servers.", + "propertyNames": { + "type": "string", + "format": "uri-template", + "minLength": 1 + }, + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/channelItem.json" + }, + "examples": [ + { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/channelItem.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/channelItem.json", + "type": "object", + "description": "Describes the operations available on a single channel.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "$ref": { + "$ref": "http://asyncapi.com/definitions/2.6.0/ReferenceObject.json" + }, + "parameters": { + "$ref": "http://asyncapi.com/definitions/2.6.0/parameters.json" + }, + "description": { + "type": "string", + "description": "A description of the channel." + }, + "servers": { + "type": "array", + "description": "The names of the servers on which this channel is available. If absent or empty then this channel must be available on all servers.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "publish": { + "$ref": "http://asyncapi.com/definitions/2.6.0/operation.json" + }, + "subscribe": { + "$ref": "http://asyncapi.com/definitions/2.6.0/operation.json" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "examples": [ + { + "description": "This channel is used to exchange messages about users signing up", + "subscribe": { + "summary": "A user signed up.", + "message": { + "description": "A longer description of the message", + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/user" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + } + } + }, + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "exclusive": true + } + } + } + }, + { + "subscribe": { + "message": { + "oneOf": [ + { + "$ref": "#/components/messages/signup" + }, + { + "$ref": "#/components/messages/login" + } + ] + } + } + }, + { + "description": "This application publishes WebUICommand messages to an AMQP queue on RabbitMQ brokers in the Staging and Production environments.", + "servers": [ + "rabbitmqBrokerInProd", + "rabbitmqBrokerInStaging" + ], + "subscribe": { + "message": { + "$ref": "#/components/messages/WebUICommand" + } + }, + "bindings": { + "amqp": { + "is": "queue" + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/parameters.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/parameters.json", + "type": "object", + "description": "JSON objects describing reusable channel parameters.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/parameter.json" + } + ] + }, + "examples": [ + { + "user/{userId}/signup": { + "parameters": { + "userId": { + "description": "Id of the user.", + "schema": { + "type": "string" + } + } + }, + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/parameter.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/parameter.json", + "description": "Describes a parameter included in a channel name.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "schema": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the parameter value", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "user/{userId}/signup": { + "parameters": { + "userId": { + "description": "Id of the user.", + "schema": { + "type": "string" + }, + "location": "$message.payload#/user/id" + } + }, + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/schema.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/schema.json", + "description": "The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays.", + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema#" + }, + { + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "items": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + }, + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + }, + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + }, + "not": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "default": {} + }, + "propertyNames": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "contains": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "discriminator": { + "type": "string", + "description": "Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. " + }, + "externalDocs": { + "description": "Additional external documentation for this schema.", + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "deprecated": { + "type": "boolean", + "default": false, + "description": "Specifies that a schema is deprecated and SHOULD be transitioned out of usage" + } + } + } + ], + "examples": [ + { + "type": "string", + "format": "email" + }, + { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "address": { + "$ref": "#/components/schemas/Address" + }, + "age": { + "type": "integer", + "format": "int32", + "minimum": 0 + } + } + } + ] + }, + "http://json-schema.org/draft-07/schema": { + "$id": "http://json-schema.org/draft-07/schema", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "propertyNames": { + "format": "regex" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#" + }, + "then": { + "$ref": "#" + }, + "else": { + "$ref": "#" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "default": true + }, + "http://asyncapi.com/definitions/2.6.0/operation.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/operation.json", + "type": "object", + "description": "Describes a publish or a subscribe operation. This provides a place to document how and why messages are sent and received.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "traits": { + "type": "array", + "description": "A list of traits to apply to the operation object.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/operationTrait.json" + } + ] + } + }, + "summary": { + "type": "string", + "description": "A short summary of what the operation is about." + }, + "description": { + "type": "string", + "description": "A verbose explanation of the operation." + }, + "security": { + "type": "array", + "description": "A declaration of which security mechanisms are associated with this operation.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json" + } + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of operations.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "operationId": { + "type": "string" + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + }, + "message": { + "$ref": "http://asyncapi.com/definitions/2.6.0/message.json" + } + }, + "examples": [ + { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/operationTrait.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/operationTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to an Operation Object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "summary": { + "type": "string", + "description": "A short summary of what the operation is about." + }, + "description": { + "type": "string", + "description": "A verbose explanation of the operation." + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of operations.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "operationId": { + "type": "string", + "description": "Unique string used to identify the operation. The id MUST be unique among all operations described in the API." + }, + "security": { + "type": "array", + "description": "A declaration of which security mechanisms are associated with this operation. ", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityRequirement.json" + } + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "examples": [ + { + "bindings": { + "amqp": { + "ack": false + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/message.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/message.json", + "description": "Describes a message received on a given channel and operation.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "oneOf": [ + { + "type": "object", + "required": [ + "oneOf" + ], + "additionalProperties": false, + "properties": { + "oneOf": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/message.json" + } + } + } + }, + { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "schemaFormat": { + "type": "string" + }, + "contentType": { + "type": "string" + }, + "headers": { + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "properties": { + "type": { + "const": "object" + } + } + } + ] + }, + "messageId": { + "type": "string" + }, + "payload": {}, + "correlationId": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "payload" + ] + }, + { + "required": [ + "headers" + ] + } + ], + "properties": { + "name": { + "type": "string", + "description": "Machine readable name of the message example." + }, + "summary": { + "type": "string", + "description": "A brief summary of the message example." + }, + "headers": { + "type": "object", + "description": "Schema definition of the application headers." + }, + "payload": { + "description": "Definition of the message payload. It can be of any type" + } + } + } + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + }, + "traits": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/messageTrait.json" + } + ] + } + } + }, + "allOf": [ + { + "if": { + "not": { + "required": [ + "schemaFormat" + ] + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.aai.asyncapi;version=2.0.0", + "application/vnd.aai.asyncapi+json;version=2.0.0", + "application/vnd.aai.asyncapi+yaml;version=2.0.0", + "application/vnd.aai.asyncapi;version=2.1.0", + "application/vnd.aai.asyncapi+json;version=2.1.0", + "application/vnd.aai.asyncapi+yaml;version=2.1.0", + "application/vnd.aai.asyncapi;version=2.2.0", + "application/vnd.aai.asyncapi+json;version=2.2.0", + "application/vnd.aai.asyncapi+yaml;version=2.2.0", + "application/vnd.aai.asyncapi;version=2.3.0", + "application/vnd.aai.asyncapi+json;version=2.3.0", + "application/vnd.aai.asyncapi+yaml;version=2.3.0", + "application/vnd.aai.asyncapi;version=2.4.0", + "application/vnd.aai.asyncapi+json;version=2.4.0", + "application/vnd.aai.asyncapi+yaml;version=2.4.0", + "application/vnd.aai.asyncapi;version=2.5.0", + "application/vnd.aai.asyncapi+json;version=2.5.0", + "application/vnd.aai.asyncapi+yaml;version=2.5.0", + "application/vnd.aai.asyncapi;version=2.6.0", + "application/vnd.aai.asyncapi+json;version=2.6.0", + "application/vnd.aai.asyncapi+yaml;version=2.6.0" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/schema+json;version=draft-07", + "application/schema+yaml;version=draft-07" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://json-schema.org/draft-07/schema" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.oai.openapi;version=3.0.0", + "application/vnd.oai.openapi+json;version=3.0.0", + "application/vnd.oai.openapi+yaml;version=3.0.0" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/openapiSchema_3_0.json" + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.apache.avro;version=1.9.0", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0" + ] + } + } + }, + "then": { + "properties": { + "payload": { + "$ref": "http://asyncapi.com/definitions/2.6.0/avroSchema_v1.json" + } + } + } + } + ] + } + ] + } + ], + "examples": [ + { + "messageId": "userSignup", + "name": "UserSignup", + "title": "User signup", + "summary": "Action to sign a user up.", + "description": "A longer description", + "contentType": "application/json", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "headers": { + "type": "object", + "properties": { + "correlationId": { + "description": "Correlation ID set by application", + "type": "string" + }, + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + }, + "correlationId": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + }, + "traits": [ + { + "$ref": "#/components/messageTraits/commonHeaders" + } + ], + "examples": [ + { + "name": "SimpleSignup", + "summary": "A simple UserSignup example message", + "headers": { + "correlationId": "my-correlation-id", + "applicationInstanceId": "myInstanceId" + }, + "payload": { + "user": { + "someUserKey": "someUserValue" + }, + "signup": { + "someSignupKey": "someSignupValue" + } + } + } + ] + }, + { + "messageId": "userSignup", + "name": "UserSignup", + "title": "User signup", + "summary": "Action to sign a user up.", + "description": "A longer description", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0", + "payload": { + "$ref": "path/to/user-create.avsc#/UserCreate" + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/correlationId.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/correlationId.json", + "type": "object", + "description": "An object that specifies an identifier at design time that can used for message tracing and correlation.", + "required": [ + "location" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A optional description of the correlation ID. GitHub Flavored Markdown is allowed." + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the correlation ID", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/messageTrait.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/messageTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to a Message Object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "schemaFormat": { + "type": "string", + "description": "A string containing the name of the schema format/language used to define the message payload." + }, + "contentType": { + "type": "string", + "description": "The content type to use when encoding/decoding a message's payload." + }, + "headers": { + "description": "Schema definition of the application headers.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + { + "properties": { + "type": { + "const": "object" + } + } + } + ] + }, + "messageId": { + "type": "string", + "description": "Unique string used to identify the message. The id MUST be unique among all messages described in the API." + }, + "correlationId": { + "description": "Definition of the correlation ID used for message tracing or matching.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of messages.", + "items": { + "$ref": "http://asyncapi.com/definitions/2.6.0/tag.json" + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "$ref": "http://asyncapi.com/definitions/2.6.0/externalDocs.json" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "payload" + ] + }, + { + "required": [ + "headers" + ] + } + ], + "properties": { + "name": { + "type": "string", + "description": "Machine readable name of the message example." + }, + "summary": { + "type": "string", + "description": "A brief summary of the message example." + }, + "headers": { + "type": "object", + "description": "Schema definition of the application headers." + }, + "payload": { + "description": "Definition of the message payload. It can be of any type" + } + } + } + }, + "bindings": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "examples": [ + { + "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0", + "contentType": "application/json" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/openapiSchema_3_0.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/openapiSchema_3_0.json", + "type": "object", + "definitions": { + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + } + }, + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": true, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": true, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": true + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/avroSchema_v1.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/avroSchema_v1.json", + "definitions": { + "avroSchema": { + "title": "Avro Schema", + "description": "Root Schema", + "oneOf": [ + { + "$ref": "#/definitions/types" + } + ] + }, + "types": { + "title": "Avro Types", + "description": "Allowed Avro types", + "oneOf": [ + { + "$ref": "#/definitions/primitiveType" + }, + { + "$ref": "#/definitions/primitiveTypeWithMetadata" + }, + { + "$ref": "#/definitions/customTypeReference" + }, + { + "$ref": "#/definitions/avroRecord" + }, + { + "$ref": "#/definitions/avroEnum" + }, + { + "$ref": "#/definitions/avroArray" + }, + { + "$ref": "#/definitions/avroMap" + }, + { + "$ref": "#/definitions/avroFixed" + }, + { + "$ref": "#/definitions/avroUnion" + } + ] + }, + "primitiveType": { + "title": "Primitive Type", + "description": "Basic type primitives.", + "type": "string", + "enum": [ + "null", + "boolean", + "int", + "long", + "float", + "double", + "bytes", + "string" + ] + }, + "primitiveTypeWithMetadata": { + "title": "Primitive Type With Metadata", + "description": "A primitive type with metadata attached.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/primitiveType" + } + }, + "required": [ + "type" + ] + }, + "customTypeReference": { + "title": "Custom Type", + "description": "Reference to a ComplexType", + "not": { + "$ref": "#/definitions/primitiveType" + }, + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$" + }, + "avroUnion": { + "title": "Union", + "description": "A Union of types", + "type": "array", + "items": { + "$ref": "#/definitions/avroSchema" + }, + "minItems": 1 + }, + "avroField": { + "title": "Field", + "description": "A field within a Record", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + }, + "type": { + "$ref": "#/definitions/types" + }, + "doc": { + "type": "string" + }, + "default": true, + "order": { + "enum": [ + "ascending", + "descending", + "ignore" + ] + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "name", + "type" + ] + }, + "avroRecord": { + "title": "Record", + "description": "A Record", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "record" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/avroField" + } + } + }, + "required": [ + "type", + "name", + "fields" + ] + }, + "avroEnum": { + "title": "Enum", + "description": "An enumeration", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "enum" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "symbols": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "type", + "name", + "symbols" + ] + }, + "avroArray": { + "title": "Array", + "description": "An array", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "array" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "items": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "items" + ] + }, + "avroMap": { + "title": "Map", + "description": "A map of values", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "map" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "values": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "values" + ] + }, + "avroFixed": { + "title": "Fixed", + "description": "A fixed sized array of bytes", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "fixed" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "size": { + "type": "number" + } + }, + "required": [ + "type", + "name", + "size" + ] + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + }, + "namespace": { + "type": "string", + "pattern": "^([A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*)*$" + } + }, + "description": "Json-Schema definition for Avro AVSC files.", + "oneOf": [ + { + "$ref": "#/definitions/avroSchema" + } + ], + "title": "Avro Schema Definition" + }, + "http://asyncapi.com/definitions/2.6.0/components.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/components.json", + "type": "object", + "description": "Holds a set of reusable objects for different aspects of the AsyncAPI specification. All objects defined within the components object will have no effect on the API unless they are explicitly referenced from properties outside the components object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "properties": { + "schemas": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schemas.json" + }, + "servers": { + "$ref": "http://asyncapi.com/definitions/2.6.0/servers.json" + }, + "channels": { + "$ref": "http://asyncapi.com/definitions/2.6.0/channels.json" + }, + "serverVariables": { + "$ref": "http://asyncapi.com/definitions/2.6.0/serverVariables.json" + }, + "messages": { + "$ref": "http://asyncapi.com/definitions/2.6.0/messages.json" + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SecurityScheme.json" + } + ] + } + } + }, + "parameters": { + "$ref": "http://asyncapi.com/definitions/2.6.0/parameters.json" + }, + "correlationIds": { + "type": "object", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/correlationId.json" + } + ] + } + } + }, + "operationTraits": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/operationTrait.json" + } + }, + "messageTraits": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/messageTrait.json" + } + }, + "serverBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "channelBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "operationBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + }, + "messageBindings": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/bindingsObject.json" + } + } + }, + "examples": [ + { + "components": { + "schemas": { + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + } + }, + "servers": { + "development": { + "url": "{stage}.gigantic-server.com:{port}", + "description": "Development server", + "protocol": "amqp", + "protocolVersion": "0.9.1", + "variables": { + "stage": { + "$ref": "#/components/serverVariables/stage" + }, + "port": { + "$ref": "#/components/serverVariables/port" + } + } + } + }, + "serverVariables": { + "stage": { + "default": "demo", + "description": "This value is assigned by the service provider, in this example `gigantic-server.com`" + }, + "port": { + "enum": [ + "8883", + "8884" + ], + "default": "8883" + } + }, + "channels": { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignUp" + } + } + } + }, + "messages": { + "userSignUp": { + "summary": "Action to sign a user up.", + "description": "Multiline description of what this action does.\nHere you have another line.\n", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + } + ], + "headers": { + "type": "object", + "properties": { + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + } + } + }, + "parameters": { + "userId": { + "description": "Id of the user.", + "schema": { + "type": "string" + } + } + }, + "correlationIds": { + "default": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + }, + "messageTraits": { + "commonHeaders": { + "headers": { + "type": "object", + "properties": { + "my-app-header": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/schemas.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/schemas.json", + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/schema.json" + }, + "description": "JSON objects describing schemas the API uses." + }, + "http://asyncapi.com/definitions/2.6.0/messages.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/messages.json", + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/2.6.0/message.json" + }, + "description": "JSON objects describing the messages being consumed and produced by the API." + }, + "http://asyncapi.com/definitions/2.6.0/SecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SecurityScheme.json", + "description": "Defines a security scheme that can be used by the operations.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/userPassword.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/apiKey.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/X509.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/symmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/asymmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/HTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flows.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/openIdConnect.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslSecurityScheme.json" + } + ], + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/userPassword.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/userPassword.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "userPassword" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/apiKey.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/apiKey.json", + "type": "object", + "required": [ + "type", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "apiKey" + ] + }, + "in": { + "type": "string", + "description": "The location of the API key. ", + "enum": [ + "user", + "password" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "apiKey", + "in": "user" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/X509.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/X509.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "X509" + ], + "description": "The type of the security scheme." + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "X509" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/symmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/symmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "symmetricEncryption" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "symmetricEncryption" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/asymmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/asymmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "asymmetricEncryption" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/HTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/HTTPSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/NonBearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/BearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/APIKeyHTTPSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/NonBearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/NonBearerHTTPSecurityScheme.json", + "not": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + "description": "A short description for security scheme.", + "enum": [ + "bearer" + ] + } + } + }, + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235." + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/BearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/BearerHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "scheme" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235.", + "enum": [ + "bearer" + ] + }, + "bearerFormat": { + "type": "string", + "description": "A hint to the client to identify how the bearer token is formatted." + }, + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "http" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/APIKeyHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/APIKeyHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. ", + "enum": [ + "httpApiKey" + ] + }, + "name": { + "type": "string", + "description": "The name of the header, query or cookie parameter to be used." + }, + "in": { + "type": "string", + "description": "The location of the API key. ", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "httpApiKey", + "name": "api_key", + "in": "header" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/oauth2Flows.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/oauth2Flows.json", + "type": "object", + "description": "Allows configuration of the supported OAuth Flows.", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "description": "A short description for security scheme.", + "enum": [ + "oauth2" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "flows": { + "type": "object", + "properties": { + "implicit": { + "description": "Configuration for the OAuth Implicit flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "scopes" + ] + }, + { + "not": { + "required": [ + "tokenUrl" + ] + } + } + ] + }, + "password": { + "description": "Configuration for the OAuth Resource Owner Protected Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "scopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "clientCredentials": { + "description": "Configuration for the OAuth Client Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "scopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "authorizationCode": { + "description": "Configuration for the OAuth Authorization Code flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ] + } + ] + } + }, + "additionalProperties": false + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + } + }, + "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/oauth2Flow.json", + "type": "object", + "description": "Configuration details for a supported OAuth Flow", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri", + "description": "The authorization URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "tokenUrl": { + "type": "string", + "format": "uri", + "description": "The token URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "refreshUrl": { + "type": "string", + "format": "uri", + "description": "The URL to be used for obtaining refresh tokens. This MUST be in the form of an absolute URL." + }, + "scopes": { + "description": "The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it.", + "$ref": "http://asyncapi.com/definitions/2.6.0/oauth2Scopes.json" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + }, + "authorizationCode": { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "tokenUrl": "https://example.com/api/oauth/token", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/oauth2Scopes.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/oauth2Scopes.json", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "http://asyncapi.com/definitions/2.6.0/openIdConnect.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/openIdConnect.json", + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "description": { + "type": "string" + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/2.6.0/SaslSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslPlainSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslScramSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/2.6.0/SaslGssapiSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/SaslPlainSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslPlainSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. Valid values", + "enum": [ + "plain" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/SaslScramSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslScramSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "scramSha256", + "scramSha512" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/2.6.0/SaslGssapiSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/2.6.0/SaslGssapiSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "gssapi" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/2.6.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + } + }, + "description": "!!Auto generated!! \n Do not manually edit. " +} \ No newline at end of file diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json new file mode 100644 index 0000000000..ed16148dc0 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.3.0.schema.json @@ -0,0 +1,8746 @@ +{ + "$id": "http://asyncapi.com/definitions/3.0.0/asyncapi.json", + "$schema": "http://json-schema.org/draft-07/schema", + "title": "AsyncAPI 3.0.0 schema.", + "type": "object", + "required": [ + "asyncapi", + "info" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "asyncapi": { + "type": "string", + "const": "3.0.0", + "description": "The AsyncAPI specification version of this document." + }, + "id": { + "type": "string", + "description": "A unique id representing the application.", + "format": "uri" + }, + "info": { + "$ref": "http://asyncapi.com/definitions/3.0.0/info.json" + }, + "servers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/servers.json" + }, + "defaultContentType": { + "type": "string", + "description": "Default content type to use when encoding/decoding a message's payload." + }, + "channels": { + "$ref": "http://asyncapi.com/definitions/3.0.0/channels.json" + }, + "operations": { + "$ref": "http://asyncapi.com/definitions/3.0.0/operations.json" + }, + "components": { + "$ref": "http://asyncapi.com/definitions/3.0.0/components.json" + } + }, + "definitions": { + "http://asyncapi.com/definitions/3.0.0/specificationExtension.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json", + "description": "Any property starting with x- is valid.", + "additionalProperties": true, + "additionalItems": true + }, + "http://asyncapi.com/definitions/3.0.0/info.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/info.json", + "type": "object", + "description": "The object provides metadata about the API. The metadata can be used by the clients if needed.", + "required": [ + "version", + "title" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "type": "string", + "description": "A unique and precise title of the API." + }, + "version": { + "type": "string", + "description": "A semantic version number of the API." + }, + "description": { + "type": "string", + "description": "A longer description of the API. Should be different from the title. CommonMark is allowed." + }, + "termsOfService": { + "type": "string", + "description": "A URL to the Terms of Service for the API. MUST be in the format of a URL.", + "format": "uri" + }, + "contact": { + "$ref": "http://asyncapi.com/definitions/3.0.0/contact.json" + }, + "license": { + "$ref": "http://asyncapi.com/definitions/3.0.0/license.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for application API documentation control. Tags can be used for logical grouping of applications.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } + }, + "examples": [ + { + "title": "AsyncAPI Sample App", + "version": "1.0.1", + "description": "This is a sample app.", + "termsOfService": "https://asyncapi.org/terms/", + "contact": { + "name": "API Support", + "url": "https://www.asyncapi.org/support", + "email": "support@asyncapi.org" + }, + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + }, + "externalDocs": { + "description": "Find more info here", + "url": "https://www.asyncapi.org" + }, + "tags": [ + { + "name": "e-commerce" + } + ] + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/contact.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/contact.json", + "type": "object", + "description": "Contact information for the exposed API.", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The identifying name of the contact person/organization." + }, + "url": { + "type": "string", + "description": "The URL pointing to the contact information.", + "format": "uri" + }, + "email": { + "type": "string", + "description": "The email address of the contact person/organization.", + "format": "email" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "API Support", + "url": "https://www.example.com/support", + "email": "support@example.com" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/license.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/license.json", + "type": "object", + "required": [ + "name" + ], + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "The name of the license type. It's encouraged to use an OSI compatible license." + }, + "url": { + "type": "string", + "description": "The URL pointing to the license.", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/Reference.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/Reference.json", + "type": "object", + "description": "A simple object to allow referencing other components in the specification, internally and externally.", + "required": [ + "$ref" + ], + "properties": { + "$ref": { + "description": "The reference string.", + "$ref": "http://asyncapi.com/definitions/3.0.0/ReferenceObject.json" + } + }, + "examples": [ + { + "$ref": "#/components/schemas/Pet" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/ReferenceObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/ReferenceObject.json", + "type": "string", + "format": "uri-reference" + }, + "http://asyncapi.com/definitions/3.0.0/tag.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/tag.json", + "type": "object", + "description": "Allows adding metadata to a single tag.", + "additionalProperties": false, + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of the tag." + }, + "description": { + "type": "string", + "description": "A short description for the tag. CommonMark syntax can be used for rich text representation." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "name": "user", + "description": "User-related messages" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/externalDocs.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/externalDocs.json", + "type": "object", + "additionalProperties": false, + "description": "Allows referencing an external resource for extended documentation.", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string", + "description": "A short description of the target documentation. CommonMark syntax can be used for rich text representation." + }, + "url": { + "type": "string", + "description": "The URL for the target documentation. This MUST be in the form of an absolute URL.", + "format": "uri" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "examples": [ + { + "description": "Find more info here", + "url": "https://example.com" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/servers.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/servers.json", + "description": "An object representing multiple servers.", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/server.json" + } + ] + }, + "examples": [ + { + "development": { + "host": "localhost:5672", + "description": "Development AMQP broker.", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "tags": [ + { + "name": "env:development", + "description": "This environment is meant for developers to run their own tests." + } + ] + }, + "staging": { + "host": "rabbitmq-staging.in.mycompany.com:5672", + "description": "RabbitMQ broker for the staging environment.", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "tags": [ + { + "name": "env:staging", + "description": "This environment is a replica of the production environment." + } + ] + }, + "production": { + "host": "rabbitmq.in.mycompany.com:5672", + "description": "RabbitMQ broker for the production environment.", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "tags": [ + { + "name": "env:production", + "description": "This environment is the live environment available for final users." + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/server.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/server.json", + "type": "object", + "description": "An object representing a message broker, a server or any other kind of computer program capable of sending and/or receiving data.", + "required": [ + "host", + "protocol" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "host": { + "type": "string", + "description": "The server host name. It MAY include the port. This field supports Server Variables. Variable substitutions will be made when a variable is named in {braces}." + }, + "pathname": { + "type": "string", + "description": "The path to a resource in the host. This field supports Server Variables. Variable substitutions will be made when a variable is named in {braces}." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the server." + }, + "summary": { + "type": "string", + "description": "A brief summary of the server." + }, + "description": { + "type": "string", + "description": "A longer description of the server. CommonMark is allowed." + }, + "protocol": { + "type": "string", + "description": "The protocol this server supports for connection." + }, + "protocolVersion": { + "type": "string", + "description": "An optional string describing the server. CommonMark syntax MAY be used for rich text representation." + }, + "variables": { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverVariables.json" + }, + "security": { + "$ref": "http://asyncapi.com/definitions/3.0.0/securityRequirements.json" + }, + "tags": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "host": "kafka.in.mycompany.com:9092", + "description": "Production Kafka broker.", + "protocol": "kafka", + "protocolVersion": "3.2" + }, + { + "host": "rabbitmq.in.mycompany.com:5672", + "pathname": "/production", + "protocol": "amqp", + "description": "Production RabbitMQ broker (uses the `production` vhost)." + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/serverVariables.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/serverVariables.json", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverVariable.json" + } + ] + } + }, + "http://asyncapi.com/definitions/3.0.0/serverVariable.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/serverVariable.json", + "type": "object", + "description": "An object representing a Server Variable for server URL template substitution.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "enum": { + "type": "array", + "description": "An enumeration of string values to be used if the substitution options are from a limited set.", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "default": { + "type": "string", + "description": "The default value to use for substitution, and to send, if an alternate value is not supplied." + }, + "description": { + "type": "string", + "description": "An optional description for the server variable. CommonMark syntax MAY be used for rich text representation." + }, + "examples": { + "type": "array", + "description": "An array of examples of the server variable.", + "items": { + "type": "string" + } + } + }, + "examples": [ + { + "host": "rabbitmq.in.mycompany.com:5672", + "pathname": "/{env}", + "protocol": "amqp", + "description": "RabbitMQ broker. Use the `env` variable to point to either `production` or `staging`.", + "variables": { + "env": { + "description": "Environment to connect to. It can be either `production` or `staging`.", + "enum": [ + "production", + "staging" + ] + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/securityRequirements.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/securityRequirements.json", + "description": "An array representing security requirements.", + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json" + } + ] + } + }, + "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json", + "description": "Defines a security scheme that can be used by the operations.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/userPassword.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/apiKey.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/X509.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/symmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/asymmetricEncryption.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/HTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flows.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/openIdConnect.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslSecurityScheme.json" + } + ], + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/userPassword.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/userPassword.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "userPassword" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "userPassword" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/apiKey.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/apiKey.json", + "type": "object", + "required": [ + "type", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme", + "enum": [ + "apiKey" + ] + }, + "in": { + "type": "string", + "description": " The location of the API key.", + "enum": [ + "user", + "password" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "apiKey", + "in": "user" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/X509.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/X509.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "X509" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "X509" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/symmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/symmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "symmetricEncryption" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "symmetricEncryption" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/asymmetricEncryption.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/asymmetricEncryption.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "asymmetricEncryption" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/HTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/HTTPSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/NonBearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/BearerHTTPSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/APIKeyHTTPSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/NonBearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/NonBearerHTTPSecurityScheme.json", + "not": { + "type": "object", + "properties": { + "scheme": { + "type": "string", + "description": "A short description for security scheme.", + "enum": [ + "bearer" + ] + } + } + }, + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235." + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/BearerHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/BearerHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "scheme" + ], + "properties": { + "scheme": { + "type": "string", + "description": "The name of the HTTP Authorization scheme to be used in the Authorization header as defined in RFC7235.", + "enum": [ + "bearer" + ] + }, + "bearerFormat": { + "type": "string", + "description": "A hint to the client to identify how the bearer token is formatted. Bearer tokens are usually generated by an authorization server, so this information is primarily for documentation purposes." + }, + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "http" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/APIKeyHTTPSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/APIKeyHTTPSecurityScheme.json", + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "httpApiKey" + ] + }, + "name": { + "type": "string", + "description": "The name of the header, query or cookie parameter to be used." + }, + "in": { + "type": "string", + "description": "The location of the API key", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "httpApiKey", + "name": "api_key", + "in": "header" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/oauth2Flows.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/oauth2Flows.json", + "type": "object", + "description": "Allows configuration of the supported OAuth Flows.", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "oauth2" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + }, + "flows": { + "type": "object", + "properties": { + "implicit": { + "description": "Configuration for the OAuth Implicit flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "availableScopes" + ] + }, + { + "not": { + "required": [ + "tokenUrl" + ] + } + } + ] + }, + "password": { + "description": "Configuration for the OAuth Resource Owner Protected Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "availableScopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "clientCredentials": { + "description": "Configuration for the OAuth Client Credentials flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "tokenUrl", + "availableScopes" + ] + }, + { + "not": { + "required": [ + "authorizationUrl" + ] + } + } + ] + }, + "authorizationCode": { + "description": "Configuration for the OAuth Authorization Code flow.", + "allOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json" + }, + { + "required": [ + "authorizationUrl", + "tokenUrl", + "availableScopes" + ] + } + ] + } + }, + "additionalProperties": false + }, + "scopes": { + "type": "array", + "description": "List of the needed scope names.", + "items": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + } + }, + "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/oauth2Flow.json", + "type": "object", + "description": "Configuration details for a supported OAuth Flow", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri", + "description": "The authorization URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "tokenUrl": { + "type": "string", + "format": "uri", + "description": "The token URL to be used for this flow. This MUST be in the form of an absolute URL." + }, + "refreshUrl": { + "type": "string", + "format": "uri", + "description": "The URL to be used for obtaining refresh tokens. This MUST be in the form of an absolute URL." + }, + "availableScopes": { + "$ref": "http://asyncapi.com/definitions/3.0.0/oauth2Scopes.json", + "description": "The available scopes for the OAuth2 security scheme. A map between the scope name and a short description for it." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "authorizationUrl": "https://example.com/api/oauth/dialog", + "tokenUrl": "https://example.com/api/oauth/token", + "availableScopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/oauth2Scopes.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/oauth2Scopes.json", + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "http://asyncapi.com/definitions/3.0.0/openIdConnect.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/openIdConnect.json", + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "openIdConnect" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme. CommonMark syntax MAY be used for rich text representation." + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri", + "description": "OpenId Connect URL to discover OAuth2 configuration values. This MUST be in the form of an absolute URL." + }, + "scopes": { + "type": "array", + "description": "List of the needed scope names. An empty array means no scopes are needed.", + "items": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/SaslSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslSecurityScheme.json", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslPlainSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslScramSecurityScheme.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SaslGssapiSecurityScheme.json" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/SaslPlainSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslPlainSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme. Valid values", + "enum": [ + "plain" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/SaslScramSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslScramSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "scramSha256", + "scramSha512" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/SaslGssapiSecurityScheme.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/SaslGssapiSecurityScheme.json", + "type": "object", + "required": [ + "type" + ], + "properties": { + "type": { + "type": "string", + "description": "The type of the security scheme.", + "enum": [ + "gssapi" + ] + }, + "description": { + "type": "string", + "description": "A short description for security scheme." + } + }, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": false, + "examples": [ + { + "type": "scramSha512" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a server.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": {}, + "ws": {}, + "amqp": {}, + "amqp1": {}, + "mqtt": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/server.json" + } + } + ] + }, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/server.json" + } + } + ] + }, + "anypointmq": {}, + "nats": {}, + "jms": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/server.json" + } + } + ] + }, + "sns": {}, + "sqs": {}, + "stomp": {}, + "redis": {}, + "ibmmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json" + } + } + ] + }, + "solace": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0", + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.3.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.2.0/server.json" + } + } + ] + }, + "googlepubsub": {}, + "pulsar": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/server.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/server.json" + } + } + ] + } + } + }, + "http://asyncapi.com/bindings/mqtt/0.2.0/server.json": { + "$id": "http://asyncapi.com/bindings/mqtt/0.2.0/server.json", + "title": "Server Schema", + "description": "This object contains information about the server representation in MQTT.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "clientId": { + "type": "string", + "description": "The client identifier." + }, + "cleanSession": { + "type": "boolean", + "description": "Whether to create a persistent connection or not. When 'false', the connection will be persistent. This is called clean start in MQTTv5." + }, + "lastWill": { + "type": "object", + "description": "Last Will and Testament configuration.", + "properties": { + "topic": { + "type": "string", + "description": "The topic where the Last Will and Testament message will be sent." + }, + "qos": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "Defines how hard the broker/client will try to ensure that the Last Will and Testament message is received. Its value MUST be either 0, 1 or 2." + }, + "message": { + "type": "string", + "description": "Last Will message." + }, + "retain": { + "type": "boolean", + "description": "Whether the broker should retain the Last Will and Testament message or not." + } + } + }, + "keepAlive": { + "type": "integer", + "description": "Interval in seconds of the longest period of time the broker and the client can endure without sending a message." + }, + "sessionExpiryInterval": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Interval time in seconds or a Schema Object containing the definition of the interval. The broker maintains a session for a disconnected client until this interval expires." + }, + "maximumPacketSize": { + "oneOf": [ + { + "type": "integer", + "minimum": 1, + "maximum": 4294967295 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Number of bytes or a Schema Object representing the Maximum Packet Size the Client is willing to accept." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "clientId": "guest", + "cleanSession": true, + "lastWill": { + "topic": "/last-wills", + "qos": 2, + "message": "Guest gone offline.", + "retain": false + }, + "keepAlive": 60, + "sessionExpiryInterval": 120, + "maximumPacketSize": 1024, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/schema.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "The Schema Object allows the definition of input and output data types. These types can be objects, but also primitives and arrays. This object is a superset of the JSON Schema Specification Draft 07. The empty schema (which allows any instance to validate) MAY be represented by the boolean value true and a schema which allows no instance to validate MAY be represented by the boolean value false.", + "allOf": [ + { + "$ref": "http://json-schema.org/draft-07/schema#" + }, + { + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "type": "boolean" + } + ], + "default": {} + }, + "items": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + } + ], + "default": {} + }, + "allOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + }, + "oneOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + }, + "anyOf": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + }, + "not": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "default": {} + }, + "propertyNames": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "contains": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "discriminator": { + "type": "string", + "description": "Adds support for polymorphism. The discriminator is the schema property name that is used to differentiate between other schema that inherit this schema. The property name used MUST be defined at this schema and it MUST be in the required property list. When used, the value MUST be the name of this schema or any schema that inherits it. See Composition and Inheritance for more details." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "deprecated": { + "type": "boolean", + "description": "Specifies that a schema is deprecated and SHOULD be transitioned out of usage. Default value is false.", + "default": false + } + } + } + ] + }, + "http://json-schema.org/draft-07/schema": { + "$id": "http://json-schema.org/draft-07/schema", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": true + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "definitions": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#" + }, + "propertyNames": { + "format": "regex" + }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/stringArray" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "contentMediaType": { + "type": "string" + }, + "contentEncoding": { + "type": "string" + }, + "if": { + "$ref": "#" + }, + "then": { + "$ref": "#" + }, + "else": { + "$ref": "#" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "default": true + }, + "http://asyncapi.com/bindings/kafka/0.4.0/server.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/server.json", + "title": "Server Schema", + "description": "This object contains server connection information to a Kafka broker. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemaRegistryUrl": { + "type": "string", + "description": "API URL for the Schema Registry used when producing Kafka messages (if a Schema Registry was used)." + }, + "schemaRegistryVendor": { + "type": "string", + "description": "The vendor of the Schema Registry and Kafka serdes library that should be used." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "schemaRegistryUrl": "https://my-schema-registry.com", + "schemaRegistryVendor": "confluent", + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/server.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/server.json", + "title": "Server Schema", + "description": "This object contains server connection information to a Kafka broker. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemaRegistryUrl": { + "type": "string", + "description": "API URL for the Schema Registry used when producing Kafka messages (if a Schema Registry was used)." + }, + "schemaRegistryVendor": { + "type": "string", + "description": "The vendor of the Schema Registry and Kafka serdes library that should be used." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "schemaRegistryUrl": "https://my-schema-registry.com", + "schemaRegistryVendor": "confluent", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/jms/0.0.1/server.json": { + "$id": "http://asyncapi.com/bindings/jms/0.0.1/server.json", + "title": "Server Schema", + "description": "This object contains configuration for describing a JMS broker as an AsyncAPI server. This objects only contains configuration that can not be provided in the AsyncAPI standard server object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "jmsConnectionFactory" + ], + "properties": { + "jmsConnectionFactory": { + "type": "string", + "description": "The classname of the ConnectionFactory implementation for the JMS Provider." + }, + "properties": { + "type": "array", + "items": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/server.json#/definitions/property" + }, + "description": "Additional properties to set on the JMS ConnectionFactory implementation for the JMS Provider." + }, + "clientID": { + "type": "string", + "description": "A client identifier for applications that use this JMS connection factory. If the Client ID Policy is set to 'Restricted' (the default), then configuring a Client ID on the ConnectionFactory prevents more than one JMS client from using a connection from this factory." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "definitions": { + "property": { + "type": "object", + "required": [ + "name", + "value" + ], + "properties": { + "name": { + "type": "string", + "description": "The name of a property" + }, + "value": { + "type": [ + "string", + "boolean", + "number", + "null" + ], + "description": "The name of a property" + } + } + } + }, + "examples": [ + { + "jmsConnectionFactory": "org.apache.activemq.ActiveMQConnectionFactory", + "properties": [ + { + "name": "disableTimeStampsByDefault", + "value": false + } + ], + "clientID": "my-application-1", + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json": { + "$id": "http://asyncapi.com/bindings/ibmmq/0.1.0/server.json", + "title": "IBM MQ server bindings object", + "description": "This object contains server connection information about the IBM MQ server, referred to as an IBM MQ queue manager. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "groupId": { + "type": "string", + "description": "Defines a logical group of IBM MQ server objects. This is necessary to specify multi-endpoint configurations used in high availability deployments. If omitted, the server object is not part of a group." + }, + "ccdtQueueManagerName": { + "type": "string", + "default": "*", + "description": "The name of the IBM MQ queue manager to bind to in the CCDT file." + }, + "cipherSpec": { + "type": "string", + "description": "The recommended cipher specification used to establish a TLS connection between the client and the IBM MQ queue manager. More information on SSL/TLS cipher specifications supported by IBM MQ can be found on this page in the IBM MQ Knowledge Center." + }, + "multiEndpointServer": { + "type": "boolean", + "default": false, + "description": "If 'multiEndpointServer' is 'true' then multiple connections can be workload balanced and applications should not make assumptions as to where messages are processed. Where message ordering, or affinity to specific message resources is necessary, a single endpoint ('multiEndpointServer' = 'false') may be required." + }, + "heartBeatInterval": { + "type": "integer", + "minimum": 0, + "maximum": 999999, + "default": 300, + "description": "The recommended value (in seconds) for the heartbeat sent to the queue manager during periods of inactivity. A value of zero means that no heart beats are sent. A value of 1 means that the client will use the value defined by the queue manager. More information on heart beat interval can be found on this page in the IBM MQ Knowledge Center." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "groupId": "PRODCLSTR1", + "cipherSpec": "ANY_TLS12_OR_HIGHER", + "bindingVersion": "0.1.0" + }, + { + "groupId": "PRODCLSTR1", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/solace/0.4.0/server.json": { + "$id": "http://asyncapi.com/bindings/solace/0.4.0/server.json", + "title": "Solace server bindings object", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "msgVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "clientName": { + "type": "string", + "minLength": 1, + "maxLength": 160, + "description": "A unique client name to use to register to the appliance. If specified, it must be a valid Topic name, and a maximum of 160 bytes in length when encoded as UTF-8." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/solace/0.3.0/server.json": { + "$id": "http://asyncapi.com/bindings/solace/0.3.0/server.json", + "title": "Solace server bindings object", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "msgVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/solace/0.2.0/server.json": { + "$id": "http://asyncapi.com/bindings/solace/0.2.0/server.json", + "title": "Solace server bindings object", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "msvVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/pulsar/0.1.0/server.json": { + "$id": "http://asyncapi.com/bindings/pulsar/0.1.0/server.json", + "title": "Server Schema", + "description": "This object contains server information of Pulsar broker, which covers cluster and tenant admin configuration. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "tenant": { + "type": "string", + "description": "The pulsar tenant. If omitted, 'public' MUST be assumed." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "tenant": "contoso", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channels.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channels.json", + "type": "object", + "description": "An object containing all the Channel Object definitions the Application MUST use during runtime.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channel.json" + } + ] + }, + "examples": [ + { + "userSignedUp": { + "address": "user.signedup", + "messages": { + "userSignedUp": { + "$ref": "#/components/messages/userSignedUp" + } + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channel.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channel.json", + "type": "object", + "description": "Describes a shared communication channel.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "address": { + "type": [ + "string", + "null" + ], + "description": "An optional string representation of this channel's address. The address is typically the \"topic name\", \"routing key\", \"event type\", or \"path\". When `null` or absent, it MUST be interpreted as unknown. This is useful when the address is generated dynamically at runtime or can't be known upfront. It MAY contain Channel Address Expressions." + }, + "messages": { + "$ref": "http://asyncapi.com/definitions/3.0.0/channelMessages.json" + }, + "parameters": { + "$ref": "http://asyncapi.com/definitions/3.0.0/parameters.json" + }, + "title": { + "type": "string", + "description": "A human-friendly title for the channel." + }, + "summary": { + "type": "string", + "description": "A brief summary of the channel." + }, + "description": { + "type": "string", + "description": "A longer description of the channel. CommonMark is allowed." + }, + "servers": { + "type": "array", + "description": "The references of the servers on which this channel is available. If absent or empty then this channel must be available on all servers.", + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + "uniqueItems": true + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping of channels.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "address": "users.{userId}", + "title": "Users channel", + "description": "This channel is used to exchange messages about user events.", + "messages": { + "userSignedUp": { + "$ref": "#/components/messages/userSignedUp" + }, + "userCompletedOrder": { + "$ref": "#/components/messages/userCompletedOrder" + } + }, + "parameters": { + "userId": { + "$ref": "#/components/parameters/userId" + } + }, + "servers": [ + { + "$ref": "#/servers/rabbitmqInProd" + }, + { + "$ref": "#/servers/rabbitmqInStaging" + } + ], + "bindings": { + "amqp": { + "is": "queue", + "queue": { + "exclusive": true + } + } + }, + "tags": [ + { + "name": "user", + "description": "User-related messages" + } + ], + "externalDocs": { + "description": "Find more info here", + "url": "https://example.com" + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channelMessages.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channelMessages.json", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageObject.json" + } + ] + }, + "description": "A map of the messages that will be sent to this channel by any application at any time. **Every message sent to this channel MUST be valid against one, and only one, of the message objects defined in this map.**" + }, + "http://asyncapi.com/definitions/3.0.0/messageObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/messageObject.json", + "type": "object", + "description": "Describes a message received on a given channel and operation.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "contentType": { + "type": "string", + "description": "The content type to use when encoding/decoding a message's payload. The value MUST be a specific media type (e.g. application/json). When omitted, the value MUST be the one specified on the defaultContentType field." + }, + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + }, + "payload": { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + }, + "correlationId": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object", + "additionalProperties": false, + "anyOf": [ + { + "required": [ + "payload" + ] + }, + { + "required": [ + "headers" + ] + } + ], + "properties": { + "name": { + "type": "string", + "description": "Machine readable name of the message example." + }, + "summary": { + "type": "string", + "description": "A brief summary of the message example." + }, + "headers": { + "type": "object", + "description": "Example of the application headers. It can be of any type." + }, + "payload": { + "description": "Example of the message payload. It can be of any type." + } + } + } + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json" + } + ] + }, + "traits": { + "type": "array", + "description": "A list of traits to apply to the message object. Traits MUST be merged using traits merge mechanism. The resulting object MUST be a valid Message Object.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageTrait.json" + }, + { + "type": "array", + "items": [ + { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageTrait.json" + } + ] + }, + { + "type": "object", + "additionalItems": true + } + ] + } + ] + } + } + }, + "examples": [ + { + "messageId": "userSignup", + "name": "UserSignup", + "title": "User signup", + "summary": "Action to sign a user up.", + "description": "A longer description", + "contentType": "application/json", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "headers": { + "type": "object", + "properties": { + "correlationId": { + "description": "Correlation ID set by application", + "type": "string" + }, + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + }, + "correlationId": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + }, + "traits": [ + { + "$ref": "#/components/messageTraits/commonHeaders" + } + ], + "examples": [ + { + "name": "SimpleSignup", + "summary": "A simple UserSignup example message", + "headers": { + "correlationId": "my-correlation-id", + "applicationInstanceId": "myInstanceId" + }, + "payload": { + "user": { + "someUserKey": "someUserValue" + }, + "signup": { + "someSignupKey": "someSignupValue" + } + } + } + ] + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/anySchema.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/anySchema.json", + "if": { + "required": [ + "schema" + ] + }, + "then": { + "$ref": "http://asyncapi.com/definitions/3.0.0/multiFormatSchema.json" + }, + "else": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "description": "An object representing either a schema or a multiFormatSchema based on the existence of the 'schema' property. If the property 'schema' is present, use the multi-format schema. Use the default AsyncAPI Schema otherwise." + }, + "http://asyncapi.com/definitions/3.0.0/multiFormatSchema.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/multiFormatSchema.json", + "description": "The Multi Format Schema Object represents a schema definition. It differs from the Schema Object in that it supports multiple schema formats or languages (e.g., JSON Schema, Avro, etc.).", + "if": { + "not": { + "type": "object" + } + }, + "then": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + "else": { + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemaFormat": { + "description": "A string containing the name of the schema format that is used to define the information. If schemaFormat is missing, it MUST default to application/vnd.aai.asyncapi+json;version={{asyncapi}} where {{asyncapi}} matches the AsyncAPI Version String. In such a case, this would make the Multi Format Schema Object equivalent to the Schema Object. When using Reference Object within the schema, the schemaFormat of the resource being referenced MUST match the schemaFormat of the schema that contains the initial reference. For example, if you reference Avro schema, then schemaFormat of referencing resource and the resource being reference MUST match.", + "anyOf": [ + { + "type": "string" + }, + { + "description": "All the schema formats tooling MUST support", + "enum": [ + "application/schema+json;version=draft-07", + "application/schema+yaml;version=draft-07", + "application/vnd.aai.asyncapi;version=3.0.0", + "application/vnd.aai.asyncapi+json;version=3.0.0", + "application/vnd.aai.asyncapi+yaml;version=3.0.0" + ] + }, + { + "description": "All the schema formats tools are RECOMMENDED to support", + "enum": [ + "application/vnd.oai.openapi;version=3.0.0", + "application/vnd.oai.openapi+json;version=3.0.0", + "application/vnd.oai.openapi+yaml;version=3.0.0", + "application/vnd.apache.avro;version=1.9.0", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0", + "application/raml+yaml;version=1.0" + ] + } + ] + }, + "schema": { + "description": "Definition of the message payload. It can be of any type but defaults to Schema Object. It MUST match the schema format defined in schemaFormat, including the encoding type. E.g., Avro should be inlined as either a YAML or JSON object instead of as a string to be parsed as YAML or JSON. Non-JSON-based schemas (e.g., Protobuf or XSD) MUST be inlined as a string." + } + }, + "allOf": [ + { + "if": { + "not": { + "description": "If no schemaFormat has been defined, default to schema or reference", + "required": [ + "schemaFormat" + ] + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + ] + } + } + } + }, + { + "if": { + "description": "If schemaFormat has been defined check if it's one of the AsyncAPI Schema Object formats", + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.aai.asyncapi;version=2.0.0", + "application/vnd.aai.asyncapi+json;version=2.0.0", + "application/vnd.aai.asyncapi+yaml;version=2.0.0", + "application/vnd.aai.asyncapi;version=2.1.0", + "application/vnd.aai.asyncapi+json;version=2.1.0", + "application/vnd.aai.asyncapi+yaml;version=2.1.0", + "application/vnd.aai.asyncapi;version=2.2.0", + "application/vnd.aai.asyncapi+json;version=2.2.0", + "application/vnd.aai.asyncapi+yaml;version=2.2.0", + "application/vnd.aai.asyncapi;version=2.3.0", + "application/vnd.aai.asyncapi+json;version=2.3.0", + "application/vnd.aai.asyncapi+yaml;version=2.3.0", + "application/vnd.aai.asyncapi;version=2.4.0", + "application/vnd.aai.asyncapi+json;version=2.4.0", + "application/vnd.aai.asyncapi+yaml;version=2.4.0", + "application/vnd.aai.asyncapi;version=2.5.0", + "application/vnd.aai.asyncapi+json;version=2.5.0", + "application/vnd.aai.asyncapi+yaml;version=2.5.0", + "application/vnd.aai.asyncapi;version=2.6.0", + "application/vnd.aai.asyncapi+json;version=2.6.0", + "application/vnd.aai.asyncapi+yaml;version=2.6.0", + "application/vnd.aai.asyncapi;version=3.0.0", + "application/vnd.aai.asyncapi+json;version=3.0.0", + "application/vnd.aai.asyncapi+yaml;version=3.0.0" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + } + ] + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/schema+json;version=draft-07", + "application/schema+yaml;version=draft-07" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://json-schema.org/draft-07/schema" + } + ] + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.oai.openapi;version=3.0.0", + "application/vnd.oai.openapi+json;version=3.0.0", + "application/vnd.oai.openapi+yaml;version=3.0.0" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/openapiSchema_3_0.json" + } + ] + } + } + } + }, + { + "if": { + "required": [ + "schemaFormat" + ], + "properties": { + "schemaFormat": { + "enum": [ + "application/vnd.apache.avro;version=1.9.0", + "application/vnd.apache.avro+json;version=1.9.0", + "application/vnd.apache.avro+yaml;version=1.9.0" + ] + } + } + }, + "then": { + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json" + } + ] + } + } + } + } + ] + } + }, + "http://asyncapi.com/definitions/3.0.0/openapiSchema_3_0.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/openapiSchema_3_0.json", + "type": "object", + "definitions": { + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": {} + }, + "additionalProperties": false + } + }, + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": true, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": true, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": true + }, + "additionalProperties": false + }, + "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json", + "definitions": { + "avroSchema": { + "title": "Avro Schema", + "description": "Root Schema", + "oneOf": [ + { + "$ref": "#/definitions/types" + } + ] + }, + "types": { + "title": "Avro Types", + "description": "Allowed Avro types", + "oneOf": [ + { + "$ref": "#/definitions/primitiveType" + }, + { + "$ref": "#/definitions/primitiveTypeWithMetadata" + }, + { + "$ref": "#/definitions/customTypeReference" + }, + { + "$ref": "#/definitions/avroRecord" + }, + { + "$ref": "#/definitions/avroEnum" + }, + { + "$ref": "#/definitions/avroArray" + }, + { + "$ref": "#/definitions/avroMap" + }, + { + "$ref": "#/definitions/avroFixed" + }, + { + "$ref": "#/definitions/avroUnion" + } + ] + }, + "primitiveType": { + "title": "Primitive Type", + "description": "Basic type primitives.", + "type": "string", + "enum": [ + "null", + "boolean", + "int", + "long", + "float", + "double", + "bytes", + "string" + ] + }, + "primitiveTypeWithMetadata": { + "title": "Primitive Type With Metadata", + "description": "A primitive type with metadata attached.", + "type": "object", + "properties": { + "type": { + "$ref": "#/definitions/primitiveType" + } + }, + "required": [ + "type" + ] + }, + "customTypeReference": { + "title": "Custom Type", + "description": "Reference to a ComplexType", + "not": { + "$ref": "#/definitions/primitiveType" + }, + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$" + }, + "avroUnion": { + "title": "Union", + "description": "A Union of types", + "type": "array", + "items": { + "$ref": "#/definitions/avroSchema" + }, + "minItems": 1 + }, + "avroField": { + "title": "Field", + "description": "A field within a Record", + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/name" + }, + "type": { + "$ref": "#/definitions/types" + }, + "doc": { + "type": "string" + }, + "default": true, + "order": { + "enum": [ + "ascending", + "descending", + "ignore" + ] + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "name", + "type" + ] + }, + "avroRecord": { + "title": "Record", + "description": "A Record", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "record" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "fields": { + "type": "array", + "items": { + "$ref": "#/definitions/avroField" + } + } + }, + "required": [ + "type", + "name", + "fields" + ] + }, + "avroEnum": { + "title": "Enum", + "description": "An enumeration", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "enum" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "symbols": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + } + }, + "required": [ + "type", + "name", + "symbols" + ] + }, + "avroArray": { + "title": "Array", + "description": "An array", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "array" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "items": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "items" + ] + }, + "avroMap": { + "title": "Map", + "description": "A map of values", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "map" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "values": { + "$ref": "#/definitions/types" + } + }, + "required": [ + "type", + "values" + ] + }, + "avroFixed": { + "title": "Fixed", + "description": "A fixed sized array of bytes", + "type": "object", + "properties": { + "type": { + "type": "string", + "const": "fixed" + }, + "name": { + "$ref": "#/definitions/name" + }, + "namespace": { + "$ref": "#/definitions/namespace" + }, + "doc": { + "type": "string" + }, + "aliases": { + "type": "array", + "items": { + "$ref": "#/definitions/name" + } + }, + "size": { + "type": "number" + } + }, + "required": [ + "type", + "name", + "size" + ] + }, + "name": { + "type": "string", + "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" + }, + "namespace": { + "type": "string", + "pattern": "^([A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*)*$" + } + }, + "description": "Json-Schema definition for Avro AVSC files.", + "oneOf": [ + { + "$ref": "#/definitions/avroSchema" + } + ], + "title": "Avro Schema Definition" + }, + "http://asyncapi.com/definitions/3.0.0/correlationId.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/correlationId.json", + "type": "object", + "description": "An object that specifies an identifier at design time that can used for message tracing and correlation.", + "required": [ + "location" + ], + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A optional description of the correlation ID. GitHub Flavored Markdown is allowed." + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the correlation ID", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a message.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.3.0/message.json" + } + } + ] + }, + "ws": {}, + "amqp": { + "properties": { + "bindingVersion": { + "enum": [ + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/message.json" + } + } + ] + }, + "amqp1": {}, + "mqtt": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/message.json" + } + } + ] + }, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/message.json" + } + } + ] + }, + "anypointmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json" + } + } + ] + }, + "nats": {}, + "jms": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/message.json" + } + } + ] + }, + "sns": {}, + "sqs": {}, + "stomp": {}, + "redis": {}, + "ibmmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json" + } + } + ] + }, + "solace": {}, + "googlepubsub": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json" + } + } + ] + } + } + }, + "http://asyncapi.com/bindings/http/0.2.0/message.json": { + "$id": "http://asyncapi.com/bindings/http/0.2.0/message.json", + "title": "HTTP message bindings object", + "description": "This object contains information about the message representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "\tA Schema object containing the definitions for HTTP-specific headers. This schema MUST be of type 'object' and have a 'properties' key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "Content-Type": { + "type": "string", + "enum": [ + "application/json" + ] + } + } + }, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/http/0.3.0/message.json": { + "$id": "http://asyncapi.com/bindings/http/0.3.0/message.json", + "title": "HTTP message bindings object", + "description": "This object contains information about the message representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "\tA Schema object containing the definitions for HTTP-specific headers. This schema MUST be of type 'object' and have a 'properties' key." + }, + "statusCode": { + "type": "number", + "description": "The HTTP response status code according to [RFC 9110](https://httpwg.org/specs/rfc9110.html#overview.of.status.codes). `statusCode` is only relevant for messages referenced by the [Operation Reply Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#operationReplyObject), as it defines the status code for the response. In all other cases, this value can be safely ignored." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "Content-Type": { + "type": "string", + "enum": [ + "application/json" + ] + } + } + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/amqp/0.3.0/message.json": { + "$id": "http://asyncapi.com/bindings/amqp/0.3.0/message.json", + "title": "AMQP message bindings object", + "description": "This object contains information about the message representation in AMQP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "contentEncoding": { + "type": "string", + "description": "A MIME encoding for the message content." + }, + "messageType": { + "type": "string", + "description": "Application-specific message type." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "contentEncoding": "gzip", + "messageType": "user.signup", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/mqtt/0.2.0/message.json": { + "$id": "http://asyncapi.com/bindings/mqtt/0.2.0/message.json", + "title": "MQTT message bindings object", + "description": "This object contains information about the message representation in MQTT.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "payloadFormatIndicator": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "description": "1 indicates that the payload is UTF-8 encoded character data. 0 indicates that the payload format is unspecified.", + "default": 0 + }, + "correlationData": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Correlation Data is used by the sender of the request message to identify which request the response message is for when it is received." + }, + "contentType": { + "type": "string", + "description": "String describing the content type of the message payload. This should not conflict with the contentType field of the associated AsyncAPI Message object." + }, + "responseTopic": { + "oneOf": [ + { + "type": "string", + "format": "uri-template", + "minLength": 1 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "The topic (channel URI) to be used for a response message." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "bindingVersion": "0.2.0" + }, + { + "contentType": "application/json", + "correlationData": { + "type": "string", + "format": "uuid" + }, + "responseTopic": "application/responses", + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.4.0/message.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/message.json", + "title": "Message Schema", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "key": { + "anyOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/avroSchema_v1.json" + } + ], + "description": "The message key." + }, + "schemaIdLocation": { + "type": "string", + "description": "If a Schema Registry is used when performing this operation, tells where the id of schema is stored.", + "enum": [ + "header", + "payload" + ] + }, + "schemaIdPayloadEncoding": { + "type": "string", + "description": "Number of bytes or vendor specific values when schema id is encoded in payload." + }, + "schemaLookupStrategy": { + "type": "string", + "description": "Freeform string for any naming strategy class to use. Clients should default to the vendor default if not supplied." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "key": { + "type": "string", + "enum": [ + "myKey" + ] + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "apicurio-new", + "schemaLookupStrategy": "TopicIdStrategy", + "bindingVersion": "0.4.0" + }, + { + "key": { + "$ref": "path/to/user-create.avsc#/UserCreate" + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "4", + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/message.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/message.json", + "title": "Message Schema", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "key": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "The message key." + }, + "schemaIdLocation": { + "type": "string", + "description": "If a Schema Registry is used when performing this operation, tells where the id of schema is stored.", + "enum": [ + "header", + "payload" + ] + }, + "schemaIdPayloadEncoding": { + "type": "string", + "description": "Number of bytes or vendor specific values when schema id is encoded in payload." + }, + "schemaLookupStrategy": { + "type": "string", + "description": "Freeform string for any naming strategy class to use. Clients should default to the vendor default if not supplied." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "key": { + "type": "string", + "enum": [ + "myKey" + ] + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "apicurio-new", + "schemaLookupStrategy": "TopicIdStrategy", + "bindingVersion": "0.3.0" + }, + { + "key": { + "$ref": "path/to/user-create.avsc#/UserCreate" + }, + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "4", + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json": { + "$id": "http://asyncapi.com/bindings/anypointmq/0.0.1/message.json", + "title": "Anypoint MQ message bindings object", + "description": "This object contains configuration for describing an Anypoint MQ message as an AsyncAPI message. This objects only contains configuration that can not be provided in the AsyncAPI standard message object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "A Schema object containing the definitions for Anypoint MQ-specific headers (protocol headers). This schema MUST be of type 'object' and have a 'properties' key. Examples of Anypoint MQ protocol headers are 'messageId' and 'messageGroupId'." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "messageId": { + "type": "string" + } + } + }, + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/jms/0.0.1/message.json": { + "$id": "http://asyncapi.com/bindings/jms/0.0.1/message.json", + "title": "Message Schema", + "description": "This object contains configuration for describing a JMS message as an AsyncAPI message. This objects only contains configuration that can not be provided in the AsyncAPI standard message object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "A Schema object containing the definitions for JMS headers (protocol headers). This schema MUST be of type 'object' and have a 'properties' key. Examples of JMS protocol headers are 'JMSMessageID', 'JMSTimestamp', and 'JMSCorrelationID'." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "headers": { + "type": "object", + "required": [ + "JMSMessageID" + ], + "properties": { + "JMSMessageID": { + "type": [ + "string", + "null" + ], + "description": "A unique message identifier. This may be set by your JMS Provider on your behalf." + }, + "JMSTimestamp": { + "type": "integer", + "description": "The time the message was sent. This may be set by your JMS Provider on your behalf. The time the message was sent. The value of the timestamp is the amount of time, measured in milliseconds, that has elapsed since midnight, January 1, 1970, UTC." + }, + "JMSDeliveryMode": { + "type": "string", + "enum": [ + "PERSISTENT", + "NON_PERSISTENT" + ], + "default": "PERSISTENT", + "description": "Denotes the delivery mode for the message. This may be set by your JMS Provider on your behalf." + }, + "JMSPriority": { + "type": "integer", + "default": 4, + "description": "The priority of the message. This may be set by your JMS Provider on your behalf." + }, + "JMSExpires": { + "type": "integer", + "description": "The time at which the message expires. This may be set by your JMS Provider on your behalf. A value of zero means that the message does not expire. Any non-zero value is the amount of time, measured in milliseconds, that has elapsed since midnight, January 1, 1970, UTC, at which the message will expire." + }, + "JMSType": { + "type": [ + "string", + "null" + ], + "description": "The type of message. Some JMS providers use a message repository that contains the definitions of messages sent by applications. The 'JMSType' header field may reference a message's definition in the provider's repository. The JMS API does not define a standard message definition repository, nor does it define a naming policy for the definitions it contains. Some messaging systems require that a message type definition for each application message be created and that each message specify its type. In order to work with such JMS providers, JMS clients should assign a value to 'JMSType', whether the application makes use of it or not. This ensures that the field is properly set for those providers that require it." + }, + "JMSCorrelationID": { + "type": [ + "string", + "null" + ], + "description": "The correlation identifier of the message. A client can use the 'JMSCorrelationID' header field to link one message with another. A typical use is to link a response message with its request message. Since each message sent by a JMS provider is assigned a message ID value, it is convenient to link messages via message ID, such message ID values must start with the 'ID:' prefix. Conversely, application-specified values must not start with the 'ID:' prefix; this is reserved for provider-generated message ID values." + }, + "JMSReplyTo": { + "type": "string", + "description": "The queue or topic that the message sender expects replies to." + } + } + }, + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json": { + "$id": "http://asyncapi.com/bindings/ibmmq/0.1.0/message.json", + "title": "IBM MQ message bindings object", + "description": "This object contains information about the message representation in IBM MQ.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "type": { + "type": "string", + "enum": [ + "string", + "jms", + "binary" + ], + "default": "string", + "description": "The type of the message." + }, + "headers": { + "type": "string", + "description": "Defines the IBM MQ message headers to include with this message. More than one header can be specified as a comma separated list. Supporting information on IBM MQ message formats can be found on this [page](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=mqmd-format-mqchar8) in the IBM MQ Knowledge Center." + }, + "description": { + "type": "string", + "description": "Provides additional information for application developers: describes the message type or format." + }, + "expiry": { + "type": "integer", + "minimum": 0, + "default": 0, + "description": "The recommended setting the client should use for the TTL (Time-To-Live) of the message. This is a period of time expressed in milliseconds and set by the application that puts the message. 'expiry' values are API dependant e.g., MQI and JMS use different units of time and default values for 'unlimited'. General information on IBM MQ message expiry can be found on this [page](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=mqmd-expiry-mqlong) in the IBM MQ Knowledge Center." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "oneOf": [ + { + "properties": { + "type": { + "const": "binary" + } + } + }, + { + "properties": { + "type": { + "const": "jms" + } + }, + "not": { + "required": [ + "headers" + ] + } + }, + { + "properties": { + "type": { + "const": "string" + } + }, + "not": { + "required": [ + "headers" + ] + } + } + ], + "examples": [ + { + "type": "string", + "bindingVersion": "0.1.0" + }, + { + "type": "jms", + "description": "JMS stream message", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json": { + "$id": "http://asyncapi.com/bindings/googlepubsub/0.2.0/message.json", + "title": "Cloud Pub/Sub Channel Schema", + "description": "This object contains information about the message representation for Google Cloud Pub/Sub.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding." + }, + "attributes": { + "type": "object" + }, + "orderingKey": { + "type": "string" + }, + "schema": { + "type": "object", + "additionalItems": false, + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ] + } + }, + "examples": [ + { + "schema": { + "name": "projects/your-project-id/schemas/your-avro-schema-id" + } + }, + { + "schema": { + "name": "projects/your-project-id/schemas/your-protobuf-schema-id" + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/messageTrait.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/messageTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to a Message Object. This object MAY contain any property from the Message Object, except payload and traits.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "contentType": { + "type": "string", + "description": "The content type to use when encoding/decoding a message's payload. The value MUST be a specific media type (e.g. application/json). When omitted, the value MUST be the one specified on the defaultContentType field." + }, + "headers": { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + }, + "correlationId": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/correlationId.json" + } + ] + }, + "tags": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "summary": { + "type": "string", + "description": "A brief summary of the message." + }, + "name": { + "type": "string", + "description": "Name of the message." + }, + "title": { + "type": "string", + "description": "A human-friendly title for the message." + }, + "description": { + "type": "string", + "description": "A longer description of the message. CommonMark is allowed." + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "description": "List of examples.", + "items": { + "type": "object" + } + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "contentType": "application/json" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/parameters.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/parameters.json", + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/parameter.json" + } + ] + }, + "description": "JSON objects describing re-usable channel parameters.", + "examples": [ + { + "address": "user/{userId}/signedup", + "parameters": { + "userId": { + "description": "Id of the user." + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/parameter.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/parameter.json", + "description": "Describes a parameter included in a channel address.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "description": { + "type": "string", + "description": "A brief description of the parameter. This could contain examples of use. GitHub Flavored Markdown is allowed." + }, + "enum": { + "description": "An enumeration of string values to be used if the substitution options are from a limited set.", + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "description": "The default value to use for substitution, and to send, if an alternate value is not supplied.", + "type": "string" + }, + "examples": { + "description": "An array of examples of the parameter value.", + "type": "array", + "items": { + "type": "string" + } + }, + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the parameter value", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + } + }, + "examples": [ + { + "address": "user/{userId}/signedup", + "parameters": { + "userId": { + "description": "Id of the user.", + "location": "$message.payload#/user/id" + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for a channel.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": {}, + "ws": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/websockets/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/websockets/0.1.0/channel.json" + } + } + ] + }, + "amqp": { + "properties": { + "bindingVersion": { + "enum": [ + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/channel.json" + } + } + ] + }, + "amqp1": {}, + "mqtt": {}, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/channel.json" + } + } + ] + }, + "anypointmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json" + } + } + ] + }, + "nats": {}, + "jms": { + "properties": { + "bindingVersion": { + "enum": [ + "0.0.1" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.0.1" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/jms/0.0.1/channel.json" + } + } + ] + }, + "sns": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json" + } + } + ] + }, + "sqs": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json" + } + } + ] + }, + "stomp": {}, + "redis": {}, + "ibmmq": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json" + } + } + ] + }, + "solace": {}, + "googlepubsub": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json" + } + } + ] + }, + "pulsar": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json" + } + } + ] + } + } + }, + "http://asyncapi.com/bindings/websockets/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/websockets/0.1.0/channel.json", + "title": "WebSockets channel bindings object", + "description": "When using WebSockets, the channel represents the connection. Unlike other protocols that support multiple virtual channels (topics, routing keys, etc.) per connection, WebSockets doesn't support virtual channels or, put it another way, there's only one channel and its characteristics are strongly related to the protocol used for the handshake, i.e., HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "POST" + ], + "description": "The HTTP method to use when establishing the connection. Its value MUST be either 'GET' or 'POST'." + }, + "query": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a 'properties' key." + }, + "headers": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "A Schema object containing the definitions of the HTTP headers to use when establishing the connection. This schema MUST be of type 'object' and have a 'properties' key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "method": "POST", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/amqp/0.3.0/channel.json": { + "$id": "http://asyncapi.com/bindings/amqp/0.3.0/channel.json", + "title": "AMQP channel bindings object", + "description": "This object contains information about the channel representation in AMQP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "is": { + "type": "string", + "enum": [ + "queue", + "routingKey" + ], + "description": "Defines what type of channel is it. Can be either 'queue' or 'routingKey' (default)." + }, + "exchange": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the exchange. It MUST NOT exceed 255 characters long." + }, + "type": { + "type": "string", + "enum": [ + "topic", + "direct", + "fanout", + "default", + "headers" + ], + "description": "The type of the exchange. Can be either 'topic', 'direct', 'fanout', 'default' or 'headers'." + }, + "durable": { + "type": "boolean", + "description": "Whether the exchange should survive broker restarts or not." + }, + "autoDelete": { + "type": "boolean", + "description": "Whether the exchange should be deleted when the last queue is unbound from it." + }, + "vhost": { + "type": "string", + "default": "/", + "description": "The virtual host of the exchange. Defaults to '/'." + } + }, + "description": "When is=routingKey, this object defines the exchange properties." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "maxLength": 255, + "description": "The name of the queue. It MUST NOT exceed 255 characters long." + }, + "durable": { + "type": "boolean", + "description": "Whether the queue should survive broker restarts or not." + }, + "exclusive": { + "type": "boolean", + "description": "Whether the queue should be used only by one connection or not." + }, + "autoDelete": { + "type": "boolean", + "description": "Whether the queue should be deleted when the last consumer unsubscribes." + }, + "vhost": { + "type": "string", + "default": "/", + "description": "The virtual host of the queue. Defaults to '/'." + } + }, + "description": "When is=queue, this object defines the queue properties." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "oneOf": [ + { + "properties": { + "is": { + "const": "routingKey" + } + }, + "required": [ + "exchange" + ], + "not": { + "required": [ + "queue" + ] + } + }, + { + "properties": { + "is": { + "const": "queue" + } + }, + "required": [ + "queue" + ], + "not": { + "required": [ + "exchange" + ] + } + } + ], + "examples": [ + { + "is": "routingKey", + "exchange": { + "name": "myExchange", + "type": "topic", + "durable": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + }, + { + "is": "queue", + "queue": { + "name": "my-queue-name", + "durable": true, + "exclusive": true, + "autoDelete": false, + "vhost": "/" + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.4.0/channel.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "topic": { + "type": "string", + "description": "Kafka topic name if different from channel name." + }, + "partitions": { + "type": "integer", + "minimum": 1, + "description": "Number of partitions configured on this topic." + }, + "replicas": { + "type": "integer", + "minimum": 1, + "description": "Number of replicas configured on this topic." + }, + "topicConfiguration": { + "description": "Topic configuration properties that are relevant for the API.", + "type": "object", + "additionalProperties": false, + "properties": { + "cleanup.policy": { + "description": "The [`cleanup.policy`](https://kafka.apache.org/documentation/#topicconfigs_cleanup.policy) configuration option.", + "type": "array", + "items": { + "type": "string", + "enum": [ + "compact", + "delete" + ] + } + }, + "retention.ms": { + "description": "The [`retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_retention.ms) configuration option.", + "type": "integer", + "minimum": -1 + }, + "retention.bytes": { + "description": "The [`retention.bytes`](https://kafka.apache.org/documentation/#topicconfigs_retention.bytes) configuration option.", + "type": "integer", + "minimum": -1 + }, + "delete.retention.ms": { + "description": "The [`delete.retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_delete.retention.ms) configuration option.", + "type": "integer", + "minimum": 0 + }, + "max.message.bytes": { + "description": "The [`max.message.bytes`](https://kafka.apache.org/documentation/#topicconfigs_max.message.bytes) configuration option.", + "type": "integer", + "minimum": 0 + } + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "topic": "my-specific-topic", + "partitions": 20, + "replicas": 3, + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/channel.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "topic": { + "type": "string", + "description": "Kafka topic name if different from channel name." + }, + "partitions": { + "type": "integer", + "minimum": 1, + "description": "Number of partitions configured on this topic." + }, + "replicas": { + "type": "integer", + "minimum": 1, + "description": "Number of replicas configured on this topic." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "topic": "my-specific-topic", + "partitions": 20, + "replicas": 3, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json": { + "$id": "http://asyncapi.com/bindings/anypointmq/0.0.1/channel.json", + "title": "Anypoint MQ channel bindings object", + "description": "This object contains configuration for describing an Anypoint MQ exchange, queue, or FIFO queue as an AsyncAPI channel. This objects only contains configuration that can not be provided in the AsyncAPI standard channel object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "destination": { + "type": "string", + "description": "The destination (queue or exchange) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name in Anypoint MQ. Defaults to the channel name." + }, + "destinationType": { + "type": "string", + "enum": [ + "exchange", + "queue", + "fifo-queue" + ], + "default": "queue", + "description": "The type of destination. SHOULD be specified to document the messaging model (publish/subscribe, point-to-point, strict message ordering) supported by this channel." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "destination": "user-signup-exchg", + "destinationType": "exchange", + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/jms/0.0.1/channel.json": { + "$id": "http://asyncapi.com/bindings/jms/0.0.1/channel.json", + "title": "Channel Schema", + "description": "This object contains configuration for describing a JMS queue, or FIFO queue as an AsyncAPI channel. This objects only contains configuration that can not be provided in the AsyncAPI standard channel object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "destination": { + "type": "string", + "description": "The destination (queue) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name according to the JMS Provider. Defaults to the channel name." + }, + "destinationType": { + "type": "string", + "enum": [ + "queue", + "fifo-queue" + ], + "default": "queue", + "description": "The type of destination. SHOULD be specified to document the messaging model (point-to-point, or strict message ordering) supported by this channel." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "destination": "user-signed-up", + "destinationType": "fifo-queue", + "bindingVersion": "0.0.1" + } + ] + }, + "http://asyncapi.com/bindings/sns/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/sns/0.1.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in SNS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "name": { + "type": "string", + "description": "The name of the topic. Can be different from the channel name to allow flexibility around AWS resource naming limitations." + }, + "ordering": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json#/definitions/ordering" + }, + "policy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the topic." + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "name" + ], + "definitions": { + "ordering": { + "type": "object", + "description": "By default, we assume an unordered SNS topic. This field allows configuration of a FIFO SNS Topic.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "type": { + "type": "string", + "description": "Defines the type of SNS Topic.", + "enum": [ + "standard", + "FIFO" + ] + }, + "contentBasedDeduplication": { + "type": "boolean", + "description": "True to turn on de-duplication of messages for a channel." + } + }, + "required": [ + "type" + ] + }, + "policy": { + "type": "object", + "description": "The security policy for the SNS Topic.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this topic", + "items": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/channel.json#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SNS permission being allowed or denied e.g. sns:Publish", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "name": "my-sns-topic", + "policy": { + "statements": [ + { + "effect": "Allow", + "principal": "*", + "action": "SNS:Publish" + } + ] + } + } + ] + }, + "http://asyncapi.com/bindings/sqs/0.2.0/channel.json": { + "$id": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in SQS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "queue": { + "description": "A definition of the queue that will be used as the channel.", + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/queue" + }, + "deadLetterQueue": { + "description": "A definition of the queue that will be used for un-processable messages.", + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/queue" + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0", + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed.", + "default": "latest" + } + }, + "required": [ + "queue" + ], + "definitions": { + "queue": { + "type": "object", + "description": "A definition of a queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "name": { + "type": "string", + "description": "The name of the queue. When an SNS Operation Binding Object references an SQS queue by name, the identifier should be the one in this field." + }, + "fifoQueue": { + "type": "boolean", + "description": "Is this a FIFO queue?", + "default": false + }, + "deduplicationScope": { + "type": "string", + "enum": [ + "queue", + "messageGroup" + ], + "description": "Specifies whether message deduplication occurs at the message group or queue level. Valid values are messageGroup and queue (default).", + "default": "queue" + }, + "fifoThroughputLimit": { + "type": "string", + "enum": [ + "perQueue", + "perMessageGroupId" + ], + "description": "Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group. Valid values are perQueue (default) and perMessageGroupId.", + "default": "perQueue" + }, + "deliveryDelay": { + "type": "integer", + "description": "The number of seconds to delay before a message sent to the queue can be received. used to create a delay queue.", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "visibilityTimeout": { + "type": "integer", + "description": "The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again.", + "minimum": 0, + "maximum": 43200, + "default": 30 + }, + "receiveMessageWaitTime": { + "type": "integer", + "description": "Determines if the queue uses short polling or long polling. Set to zero the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning.", + "default": 0 + }, + "messageRetentionPeriod": { + "type": "integer", + "description": "How long to retain a message on the queue in seconds, unless deleted.", + "minimum": 60, + "maximum": 1209600, + "default": 345600 + }, + "redrivePolicy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/redrivePolicy" + }, + "policy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the queue." + } + }, + "required": [ + "name", + "fifoQueue" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/identifier" + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + }, + "identifier": { + "type": "object", + "description": "The SQS queue to use as a dead letter queue (DLQ).", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding." + } + } + }, + "policy": { + "type": "object", + "description": "The security policy for the SQS Queue", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this queue.", + "items": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/channel.json#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SQS permission being allowed or denied e.g. sqs:ReceiveMessage", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "queue": { + "name": "myQueue", + "fifoQueue": true, + "deduplicationScope": "messageGroup", + "fifoThroughputLimit": "perMessageGroupId", + "deliveryDelay": 15, + "visibilityTimeout": 60, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 86400, + "redrivePolicy": { + "deadLetterQueue": { + "arn": "arn:aws:SQS:eu-west-1:0000000:123456789" + }, + "maxReceiveCount": 15 + }, + "policy": { + "statements": [ + { + "effect": "Deny", + "principal": "arn:aws:iam::123456789012:user/dec.kolakowski", + "action": [ + "sqs:SendMessage", + "sqs:ReceiveMessage" + ] + } + ] + }, + "tags": { + "owner": "AsyncAPI.NET", + "platform": "AsyncAPIOrg" + } + }, + "deadLetterQueue": { + "name": "myQueue_error", + "deliveryDelay": 0, + "visibilityTimeout": 0, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 604800 + } + } + ] + }, + "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/ibmmq/0.1.0/channel.json", + "title": "IBM MQ channel bindings object", + "description": "This object contains information about the channel representation in IBM MQ. Each channel corresponds to a Queue or Topic within IBM MQ.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "destinationType": { + "type": "string", + "enum": [ + "topic", + "queue" + ], + "default": "topic", + "description": "Defines the type of AsyncAPI channel." + }, + "queue": { + "type": "object", + "description": "Defines the properties of a queue.", + "properties": { + "objectName": { + "type": "string", + "maxLength": 48, + "description": "Defines the name of the IBM MQ queue associated with the channel." + }, + "isPartitioned": { + "type": "boolean", + "default": false, + "description": "Defines if the queue is a cluster queue and therefore partitioned. If 'true', a binding option MAY be specified when accessing the queue. More information on binding options can be found on this page in the IBM MQ Knowledge Center." + }, + "exclusive": { + "type": "boolean", + "default": false, + "description": "Specifies if it is recommended to open the queue exclusively." + } + }, + "required": [ + "objectName" + ] + }, + "topic": { + "type": "object", + "description": "Defines the properties of a topic.", + "properties": { + "string": { + "type": "string", + "maxLength": 10240, + "description": "The value of the IBM MQ topic string to be used." + }, + "objectName": { + "type": "string", + "maxLength": 48, + "description": "The name of the IBM MQ topic object." + }, + "durablePermitted": { + "type": "boolean", + "default": true, + "description": "Defines if the subscription may be durable." + }, + "lastMsgRetained": { + "type": "boolean", + "default": false, + "description": "Defines if the last message published will be made available to new subscriptions." + } + } + }, + "maxMsgLength": { + "type": "integer", + "minimum": 0, + "maximum": 104857600, + "description": "The maximum length of the physical message (in bytes) accepted by the Topic or Queue. Messages produced that are greater in size than this value may fail to be delivered. More information on the maximum message length can be found on this [page](https://www.ibm.com/support/knowledgecenter/SSFKSJ_latest/com.ibm.mq.ref.dev.doc/q097520_.html) in the IBM MQ Knowledge Center." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "const": "topic" + } + }, + "not": { + "required": [ + "queue" + ] + } + }, + { + "properties": { + "destinationType": { + "const": "queue" + } + }, + "required": [ + "queue" + ], + "not": { + "required": [ + "topic" + ] + } + } + ], + "examples": [ + { + "destinationType": "topic", + "topic": { + "objectName": "myTopicName" + }, + "bindingVersion": "0.1.0" + }, + { + "destinationType": "queue", + "queue": { + "objectName": "myQueueName", + "exclusive": true + }, + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json": { + "$id": "http://asyncapi.com/bindings/googlepubsub/0.2.0/channel.json", + "title": "Cloud Pub/Sub Channel Schema", + "description": "This object contains information about the channel representation for Google Cloud Pub/Sub.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding." + }, + "labels": { + "type": "object" + }, + "messageRetentionDuration": { + "type": "string" + }, + "messageStoragePolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowedPersistenceRegions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schemaSettings": { + "type": "object", + "additionalItems": false, + "properties": { + "encoding": { + "type": "string" + }, + "firstRevisionId": { + "type": "string" + }, + "lastRevisionId": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "encoding", + "name" + ] + } + }, + "required": [ + "schemaSettings" + ], + "examples": [ + { + "labels": { + "label1": "value1", + "label2": "value2" + }, + "messageRetentionDuration": "86400s", + "messageStoragePolicy": { + "allowedPersistenceRegions": [ + "us-central1", + "us-east1" + ] + }, + "schemaSettings": { + "encoding": "json", + "name": "projects/your-project-id/schemas/your-schema" + } + } + ] + }, + "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json": { + "$id": "http://asyncapi.com/bindings/pulsar/0.1.0/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Pulsar, which covers namespace and topic level admin configuration. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "namespace", + "persistence" + ], + "properties": { + "namespace": { + "type": "string", + "description": "The namespace, the channel is associated with." + }, + "persistence": { + "type": "string", + "enum": [ + "persistent", + "non-persistent" + ], + "description": "persistence of the topic in Pulsar." + }, + "compaction": { + "type": "integer", + "minimum": 0, + "description": "Topic compaction threshold given in MB" + }, + "geo-replication": { + "type": "array", + "description": "A list of clusters the topic is replicated to.", + "items": { + "type": "string" + } + }, + "retention": { + "type": "object", + "additionalProperties": false, + "properties": { + "time": { + "type": "integer", + "minimum": 0, + "description": "Time given in Minutes. `0` = Disable message retention." + }, + "size": { + "type": "integer", + "minimum": 0, + "description": "Size given in MegaBytes. `0` = Disable message retention." + } + } + }, + "ttl": { + "type": "integer", + "description": "TTL in seconds for the specified topic" + }, + "deduplication": { + "type": "boolean", + "description": "Whether deduplication of events is enabled or not." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "namespace": "ns1", + "persistence": "persistent", + "compaction": 1000, + "retention": { + "time": 15, + "size": 1000 + }, + "ttl": 360, + "geo-replication": [ + "us-west", + "us-east" + ], + "deduplication": true, + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operations.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operations.json", + "type": "object", + "description": "Holds a dictionary with all the operations this application MUST implement.", + "additionalProperties": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json" + } + ] + }, + "examples": [ + { + "onUserSignUp": { + "title": "User sign up", + "summary": "Action to sign a user up.", + "description": "A longer description", + "channel": { + "$ref": "#/channels/userSignup" + }, + "action": "send", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "bindings": { + "amqp": { + "ack": false + } + }, + "traits": [ + { + "$ref": "#/components/operationTraits/kafka" + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operation.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operation.json", + "type": "object", + "description": "Describes a specific operation.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "action", + "channel" + ], + "properties": { + "action": { + "type": "string", + "description": "Allowed values are send and receive. Use send when it's expected that the application will send a message to the given channel, and receive when the application should expect receiving messages from the given channel.", + "enum": [ + "send", + "receive" + ] + }, + "channel": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + "messages": { + "type": "array", + "description": "A list of $ref pointers pointing to the supported Message Objects that can be processed by this operation. It MUST contain a subset of the messages defined in the channel referenced in this operation. Every message processed by this operation MUST be valid against one, and only one, of the message objects referenced in this list. Please note the messages property value MUST be a list of Reference Objects and, therefore, MUST NOT contain Message Objects. However, it is RECOMMENDED that parsers (or other software) dereference this property for a better development experience.", + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + }, + "reply": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReply.json" + } + ] + }, + "traits": { + "type": "array", + "description": "A list of traits to apply to the operation object. Traits MUST be merged using traits merge mechanism. The resulting object MUST be a valid Operation Object.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationTrait.json" + } + ] + } + }, + "title": { + "type": "string", + "description": "A human-friendly title for the operation." + }, + "summary": { + "type": "string", + "description": "A brief summary of the operation." + }, + "description": { + "type": "string", + "description": "A longer description of the operation. CommonMark is allowed." + }, + "security": { + "$ref": "http://asyncapi.com/definitions/3.0.0/securityRequirements.json" + }, + "tags": { + "type": "array", + "description": "A list of tags for logical grouping and categorization of operations.", + "items": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + }, + "uniqueItems": true + }, + "externalDocs": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + }, + "bindings": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "title": "User sign up", + "summary": "Action to sign a user up.", + "description": "A longer description", + "channel": { + "$ref": "#/channels/userSignup" + }, + "action": "send", + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ], + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + }, + { + "name": "register" + } + ], + "bindings": { + "amqp": { + "ack": false + } + }, + "traits": [ + { + "$ref": "#/components/operationTraits/kafka" + } + ], + "messages": [ + { + "$ref": "/components/messages/userSignedUp" + } + ], + "reply": { + "address": { + "location": "$message.header#/replyTo" + }, + "channel": { + "$ref": "#/channels/userSignupReply" + }, + "messages": [ + { + "$ref": "/components/messages/userSignedUpReply" + } + ] + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operationReply.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationReply.json", + "type": "object", + "description": "Describes the reply part that MAY be applied to an Operation Object. If an operation implements the request/reply pattern, the reply object represents the response message.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "address": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json" + } + ] + }, + "channel": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + "messages": { + "type": "array", + "description": "A list of $ref pointers pointing to the supported Message Objects that can be processed by this operation as reply. It MUST contain a subset of the messages defined in the channel referenced in this operation reply. Every message processed by this operation MUST be valid against one, and only one, of the message objects referenced in this list. Please note the messages property value MUST be a list of Reference Objects and, therefore, MUST NOT contain Message Objects. However, it is RECOMMENDED that parsers (or other software) dereference this property for a better development experience.", + "items": { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + } + } + }, + "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json", + "type": "object", + "description": "An object that specifies where an operation has to send the reply", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "required": [ + "location" + ], + "properties": { + "location": { + "type": "string", + "description": "A runtime expression that specifies the location of the reply address.", + "pattern": "^\\$message\\.(header|payload)#(\\/(([^\\/~])|(~[01]))*)*" + }, + "description": { + "type": "string", + "description": "An optional description of the address. CommonMark is allowed." + } + }, + "examples": [ + { + "description": "Consumer inbox", + "location": "$message.header#/replyTo" + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operationTrait.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationTrait.json", + "type": "object", + "description": "Describes a trait that MAY be applied to an Operation Object. This object MAY contain any property from the Operation Object, except the action, channel and traits ones.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "title": { + "description": "A human-friendly title for the operation.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/title" + }, + "summary": { + "description": "A short summary of what the operation is about.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/summary" + }, + "description": { + "description": "A verbose explanation of the operation. CommonMark syntax can be used for rich text representation.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/description" + }, + "security": { + "description": "A declaration of which security schemes are associated with this operation. Only one of the security scheme objects MUST be satisfied to authorize an operation. In cases where Server Security also applies, it MUST also be satisfied.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/security" + }, + "tags": { + "description": "A list of tags for logical grouping and categorization of operations.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/tags" + }, + "externalDocs": { + "description": "Additional external documentation for this operation.", + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json#/properties/externalDocs" + }, + "bindings": { + "description": "A map where the keys describe the name of the protocol and the values describe protocol-specific definitions for the operation.", + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json" + } + ] + } + }, + "examples": [ + { + "bindings": { + "amqp": { + "ack": false + } + } + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json", + "type": "object", + "description": "Map describing protocol-specific definitions for an operation.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "http": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/http/0.3.0/operation.json" + } + } + ] + }, + "ws": {}, + "amqp": { + "properties": { + "bindingVersion": { + "enum": [ + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/amqp/0.3.0/operation.json" + } + } + ] + }, + "amqp1": {}, + "mqtt": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json" + } + } + ] + }, + "kafka": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/kafka/0.3.0/operation.json" + } + } + ] + }, + "anypointmq": {}, + "nats": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/nats/0.1.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/nats/0.1.0/operation.json" + } + } + ] + }, + "jms": {}, + "sns": { + "properties": { + "bindingVersion": { + "enum": [ + "0.1.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.1.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json" + } + } + ] + }, + "sqs": { + "properties": { + "bindingVersion": { + "enum": [ + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json" + } + } + ] + }, + "stomp": {}, + "redis": {}, + "ibmmq": {}, + "solace": { + "properties": { + "bindingVersion": { + "enum": [ + "0.4.0", + "0.3.0", + "0.2.0" + ] + } + }, + "allOf": [ + { + "description": "If no bindingVersion specified, use the latest binding", + "if": { + "not": { + "required": [ + "bindingVersion" + ] + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.4.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.4.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.3.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.3.0/operation.json" + } + }, + { + "if": { + "required": [ + "bindingVersion" + ], + "properties": { + "bindingVersion": { + "const": "0.2.0" + } + } + }, + "then": { + "$ref": "http://asyncapi.com/bindings/solace/0.2.0/operation.json" + } + } + ] + }, + "googlepubsub": {} + } + }, + "http://asyncapi.com/bindings/http/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/http/0.2.0/operation.json", + "title": "HTTP operation bindings object", + "description": "This object contains information about the operation representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS", + "CONNECT", + "TRACE" + ], + "description": "When 'type' is 'request', this is the HTTP method, otherwise it MUST be ignored. Its value MUST be one of 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', and 'TRACE'." + }, + "query": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a properties key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.2.0" + }, + { + "method": "GET", + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/http/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/http/0.3.0/operation.json", + "title": "HTTP operation bindings object", + "description": "This object contains information about the operation representation in HTTP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "PUT", + "POST", + "PATCH", + "DELETE", + "HEAD", + "OPTIONS", + "CONNECT", + "TRACE" + ], + "description": "When 'type' is 'request', this is the HTTP method, otherwise it MUST be ignored. Its value MUST be one of 'GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS', 'CONNECT', and 'TRACE'." + }, + "query": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "A Schema object containing the definitions for each query parameter. This schema MUST be of type 'object' and have a properties key." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.3.0" + }, + { + "method": "GET", + "query": { + "type": "object", + "required": [ + "companyId" + ], + "properties": { + "companyId": { + "type": "number", + "minimum": 1, + "description": "The Id of the company." + } + }, + "additionalProperties": false + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/amqp/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/amqp/0.3.0/operation.json", + "title": "AMQP operation bindings object", + "description": "This object contains information about the operation representation in AMQP.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "expiration": { + "type": "integer", + "minimum": 0, + "description": "TTL (Time-To-Live) for the message. It MUST be greater than or equal to zero." + }, + "userId": { + "type": "string", + "description": "Identifies the user who has sent the message." + }, + "cc": { + "type": "array", + "items": { + "type": "string" + }, + "description": "The routing keys the message should be routed to at the time of publishing." + }, + "priority": { + "type": "integer", + "description": "A priority for the message." + }, + "deliveryMode": { + "type": "integer", + "enum": [ + 1, + 2 + ], + "description": "Delivery mode of the message. Its value MUST be either 1 (transient) or 2 (persistent)." + }, + "mandatory": { + "type": "boolean", + "description": "Whether the message is mandatory or not." + }, + "bcc": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Like cc but consumers will not receive this information." + }, + "timestamp": { + "type": "boolean", + "description": "Whether the message should include a timestamp or not." + }, + "ack": { + "type": "boolean", + "description": "Whether the consumer should ack the message or not." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "expiration": 100000, + "userId": "guest", + "cc": [ + "user.logs" + ], + "priority": 10, + "deliveryMode": 2, + "mandatory": false, + "bcc": [ + "external.audit" + ], + "timestamp": true, + "ack": false, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/mqtt/0.2.0/operation.json", + "title": "MQTT operation bindings object", + "description": "This object contains information about the operation representation in MQTT.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "qos": { + "type": "integer", + "enum": [ + 0, + 1, + 2 + ], + "description": "Defines the Quality of Service (QoS) levels for the message flow between client and server. Its value MUST be either 0 (At most once delivery), 1 (At least once delivery), or 2 (Exactly once delivery)." + }, + "retain": { + "type": "boolean", + "description": "Whether the broker should retain the message or not." + }, + "messageExpiryInterval": { + "oneOf": [ + { + "type": "integer", + "minimum": 0, + "maximum": 4294967295 + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + } + ], + "description": "Lifetime of the message in seconds" + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "qos": 2, + "retain": true, + "messageExpiryInterval": 60, + "bindingVersion": "0.2.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.4.0/operation.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.4.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "groupId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer group." + }, + "clientId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer inside a consumer group." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "groupId": { + "type": "string", + "enum": [ + "myGroupId" + ] + }, + "clientId": { + "type": "string", + "enum": [ + "myClientId" + ] + }, + "bindingVersion": "0.4.0" + } + ] + }, + "http://asyncapi.com/bindings/kafka/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/kafka/0.3.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "groupId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer group." + }, + "clientId": { + "$ref": "http://asyncapi.com/definitions/3.0.0/schema.json", + "description": "Id of the consumer inside a consumer group." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "groupId": { + "type": "string", + "enum": [ + "myGroupId" + ] + }, + "clientId": { + "type": "string", + "enum": [ + "myClientId" + ] + }, + "bindingVersion": "0.3.0" + } + ] + }, + "http://asyncapi.com/bindings/nats/0.1.0/operation.json": { + "$id": "http://asyncapi.com/bindings/nats/0.1.0/operation.json", + "title": "NATS operation bindings object", + "description": "This object contains information about the operation representation in NATS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "queue": { + "type": "string", + "description": "Defines the name of the queue to use. It MUST NOT exceed 255 characters.", + "maxLength": 255 + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "queue": "MyCustomQueue", + "bindingVersion": "0.1.0" + } + ] + }, + "http://asyncapi.com/bindings/sns/0.1.0/operation.json": { + "$id": "http://asyncapi.com/bindings/sns/0.1.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in SNS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "topic": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/identifier", + "description": "Often we can assume that the SNS Topic is the channel name-we provide this field in case the you need to supply the ARN, or the Topic name is not the channel name in the AsyncAPI document." + }, + "consumers": { + "type": "array", + "description": "The protocols that listen to this topic and their endpoints.", + "items": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/consumer" + }, + "minItems": 1 + }, + "deliveryPolicy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/deliveryPolicy", + "description": "Policy for retries to HTTP. The field is the default for HTTP receivers of the SNS Topic which may be overridden by a specific consumer." + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "consumers" + ], + "definitions": { + "identifier": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "url": { + "type": "string", + "description": "The endpoint is a URL." + }, + "email": { + "type": "string", + "description": "The endpoint is an email address." + }, + "phone": { + "type": "string", + "description": "The endpoint is a phone number." + }, + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding. We don't use $ref because we are referring, not including." + } + } + }, + "consumer": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "protocol": { + "description": "The protocol that this endpoint receives messages by.", + "type": "string", + "enum": [ + "http", + "https", + "email", + "email-json", + "sms", + "sqs", + "application", + "lambda", + "firehose" + ] + }, + "endpoint": { + "description": "The endpoint messages are delivered to.", + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/identifier" + }, + "filterPolicy": { + "type": "object", + "description": "Only receive a subset of messages from the channel, determined by this policy. Depending on the FilterPolicyScope, a map of either a message attribute or message body to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "additionalProperties": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + } + }, + "filterPolicyScope": { + "type": "string", + "description": "Determines whether the FilterPolicy applies to MessageAttributes or MessageBody.", + "enum": [ + "MessageAttributes", + "MessageBody" + ], + "default": "MessageAttributes" + }, + "rawMessageDelivery": { + "type": "boolean", + "description": "If true AWS SNS attributes are removed from the body, and for SQS, SNS message attributes are copied to SQS message attributes. If false the SNS attributes are included in the body." + }, + "redrivePolicy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/redrivePolicy" + }, + "deliveryPolicy": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/deliveryPolicy", + "description": "Policy for retries to HTTP. The parameter is for that SNS Subscription and overrides any policy on the SNS Topic." + }, + "displayName": { + "type": "string", + "description": "The display name to use with an SNS subscription" + } + }, + "required": [ + "protocol", + "endpoint", + "rawMessageDelivery" + ] + }, + "deliveryPolicy": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "minDelayTarget": { + "type": "integer", + "description": "The minimum delay for a retry in seconds." + }, + "maxDelayTarget": { + "type": "integer", + "description": "The maximum delay for a retry in seconds." + }, + "numRetries": { + "type": "integer", + "description": "The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries." + }, + "numNoDelayRetries": { + "type": "integer", + "description": "The number of immediate retries (with no delay)." + }, + "numMinDelayRetries": { + "type": "integer", + "description": "The number of immediate retries (with delay)." + }, + "numMaxDelayRetries": { + "type": "integer", + "description": "The number of post-backoff phase retries, with the maximum delay between retries." + }, + "backoffFunction": { + "type": "string", + "description": "The algorithm for backoff between retries.", + "enum": [ + "arithmetic", + "exponential", + "geometric", + "linear" + ] + }, + "maxReceivesPerSecond": { + "type": "integer", + "description": "The maximum number of deliveries per second, per subscription." + } + } + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "http://asyncapi.com/bindings/sns/0.1.0/operation.json#/definitions/identifier", + "description": "The SQS queue to use as a dead letter queue (DLQ)." + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + } + }, + "examples": [ + { + "topic": { + "name": "someTopic" + }, + "consumers": [ + { + "protocol": "sqs", + "endpoint": { + "name": "someQueue" + }, + "filterPolicy": { + "store": [ + "asyncapi_corp" + ], + "event": [ + { + "anything-but": "order_cancelled" + } + ], + "customer_interests": [ + "rugby", + "football", + "baseball" + ] + }, + "filterPolicyScope": "MessageAttributes", + "rawMessageDelivery": false, + "redrivePolicy": { + "deadLetterQueue": { + "arn": "arn:aws:SQS:eu-west-1:0000000:123456789" + }, + "maxReceiveCount": 25 + }, + "deliveryPolicy": { + "minDelayTarget": 10, + "maxDelayTarget": 100, + "numRetries": 5, + "numNoDelayRetries": 2, + "numMinDelayRetries": 3, + "numMaxDelayRetries": 5, + "backoffFunction": "linear", + "maxReceivesPerSecond": 2 + } + } + ] + } + ] + }, + "http://asyncapi.com/bindings/sqs/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in SQS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "queues": { + "type": "array", + "description": "Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object.", + "items": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/queue" + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0", + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed.", + "default": "latest" + } + }, + "required": [ + "queues" + ], + "definitions": { + "queue": { + "type": "object", + "description": "A definition of a queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "$ref": { + "type": "string", + "description": "Allows for an external definition of a queue. The referenced structure MUST be in the format of a Queue. If there are conflicts between the referenced definition and this Queue's definition, the behavior is undefined." + }, + "name": { + "type": "string", + "description": "The name of the queue. When an SNS Operation Binding Object references an SQS queue by name, the identifier should be the one in this field." + }, + "fifoQueue": { + "type": "boolean", + "description": "Is this a FIFO queue?", + "default": false + }, + "deduplicationScope": { + "type": "string", + "enum": [ + "queue", + "messageGroup" + ], + "description": "Specifies whether message deduplication occurs at the message group or queue level. Valid values are messageGroup and queue (default).", + "default": "queue" + }, + "fifoThroughputLimit": { + "type": "string", + "enum": [ + "perQueue", + "perMessageGroupId" + ], + "description": "Specifies whether the FIFO queue throughput quota applies to the entire queue or per message group. Valid values are perQueue (default) and perMessageGroupId.", + "default": "perQueue" + }, + "deliveryDelay": { + "type": "integer", + "description": "The number of seconds to delay before a message sent to the queue can be received. Used to create a delay queue.", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "visibilityTimeout": { + "type": "integer", + "description": "The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again.", + "minimum": 0, + "maximum": 43200, + "default": 30 + }, + "receiveMessageWaitTime": { + "type": "integer", + "description": "Determines if the queue uses short polling or long polling. Set to zero the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning.", + "default": 0 + }, + "messageRetentionPeriod": { + "type": "integer", + "description": "How long to retain a message on the queue in seconds, unless deleted.", + "minimum": 60, + "maximum": 1209600, + "default": 345600 + }, + "redrivePolicy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/redrivePolicy" + }, + "policy": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the queue." + } + }, + "required": [ + "name" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/identifier" + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + }, + "identifier": { + "type": "object", + "description": "The SQS queue to use as a dead letter queue (DLQ).", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding." + } + } + }, + "policy": { + "type": "object", + "description": "The security policy for the SQS Queue", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this queue.", + "items": { + "$ref": "http://asyncapi.com/bindings/sqs/0.2.0/operation.json#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SQS permission being allowed or denied e.g. sqs:ReceiveMessage", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "queues": [ + { + "name": "myQueue", + "fifoQueue": true, + "deduplicationScope": "messageGroup", + "fifoThroughputLimit": "perMessageGroupId", + "deliveryDelay": 10, + "redrivePolicy": { + "deadLetterQueue": { + "name": "myQueue_error" + }, + "maxReceiveCount": 15 + }, + "policy": { + "statements": [ + { + "effect": "Deny", + "principal": "arn:aws:iam::123456789012:user/dec.kolakowski", + "action": [ + "sqs:SendMessage", + "sqs:ReceiveMessage" + ] + } + ] + } + }, + { + "name": "myQueue_error", + "deliveryDelay": 10 + } + ] + } + ] + }, + "http://asyncapi.com/bindings/solace/0.4.0/operation.json": { + "$id": "http://asyncapi.com/bindings/solace/0.4.0/operation.json", + "title": "Solace operation bindings object", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.4.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + }, + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] + }, + "maxTtl": { + "type": "string", + "description": "The maximum TTL to apply to messages to be spooled." + }, + "maxMsgSpoolUsage": { + "type": "string", + "description": "The maximum amount of message spool that the given queue may use" + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "timeToLive": { + "type": "integer", + "description": "Interval in milliseconds or a Schema Object containing the definition of the lifetime of the message." + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 255, + "description": "The valid priority value range is 0-255 with 0 as the lowest priority and 255 as the highest or a Schema Object containing the definition of the priority." + }, + "dmqEligible": { + "type": "boolean", + "description": "Set the message to be eligible to be moved to a Dead Message Queue. The default value is false." + } + }, + "examples": [ + { + "bindingVersion": "0.4.0", + "destinations": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "accessType": "nonexclusive" + } + }, + { + "destinationType": "topic", + "topicSubscriptions": [ + "samples/*" + ] + } + ] + } + ] + }, + "http://asyncapi.com/bindings/solace/0.3.0/operation.json": { + "$id": "http://asyncapi.com/bindings/solace/0.3.0/operation.json", + "title": "Solace operation bindings object", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] + }, + "maxTtl": { + "type": "string", + "description": "The maximum TTL to apply to messages to be spooled." + }, + "maxMsgSpoolUsage": { + "type": "string", + "description": "The maximum amount of message spool that the given queue may use" + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "bindingVersion": "0.3.0", + "destinations": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "accessType": "nonexclusive" + } + }, + { + "destinationType": "topic", + "topicSubscriptions": [ + "samples/*" + ] + } + ] + } + ] + }, + "http://asyncapi.com/bindings/solace/0.2.0/operation.json": { + "$id": "http://asyncapi.com/bindings/solace/0.2.0/operation.json", + "title": "Solace operation bindings object", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } + } + } + ] + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + } + }, + "examples": [ + { + "bindingVersion": "0.2.0", + "destinations": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "accessType": "nonexclusive" + } + }, + { + "destinationType": "topic", + "topicSubscriptions": [ + "samples/*" + ] + } + ] + } + ] + }, + "http://asyncapi.com/definitions/3.0.0/components.json": { + "$id": "http://asyncapi.com/definitions/3.0.0/components.json", + "type": "object", + "description": "An object to hold a set of reusable objects for different aspects of the AsyncAPI specification. All objects defined within the components object will have no effect on the API unless they are explicitly referenced from properties outside the components object.", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\x2d_]+$": { + "$ref": "http://asyncapi.com/definitions/3.0.0/specificationExtension.json" + } + }, + "properties": { + "schemas": { + "type": "object", + "description": "An object to hold reusable Schema Object. If this is a Schema Object, then the schemaFormat will be assumed to be 'application/vnd.aai.asyncapi+json;version=asyncapi' where the version is equal to the AsyncAPI Version String.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/anySchema.json" + } + ] + } + } + }, + "servers": { + "type": "object", + "description": "An object to hold reusable Server Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/server.json" + } + ] + } + } + }, + "channels": { + "type": "object", + "description": "An object to hold reusable Channel Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channel.json" + } + ] + } + } + }, + "serverVariables": { + "type": "object", + "description": "An object to hold reusable Server Variable Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverVariable.json" + } + ] + } + } + }, + "operations": { + "type": "object", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operation.json" + } + ] + } + } + }, + "messages": { + "type": "object", + "description": "An object to hold reusable Message Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageObject.json" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "description": "An object to hold reusable Security Scheme Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/SecurityScheme.json" + } + ] + } + } + }, + "parameters": { + "type": "object", + "description": "An object to hold reusable Parameter Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/parameter.json" + } + ] + } + } + }, + "correlationIds": { + "type": "object", + "description": "An object to hold reusable Correlation ID Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/correlationId.json" + } + ] + } + } + }, + "operationTraits": { + "type": "object", + "description": "An object to hold reusable Operation Trait Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationTrait.json" + } + ] + } + } + }, + "messageTraits": { + "type": "object", + "description": "An object to hold reusable Message Trait Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageTrait.json" + } + ] + } + } + }, + "replies": { + "type": "object", + "description": "An object to hold reusable Operation Reply Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReply.json" + } + ] + } + } + }, + "replyAddresses": { + "type": "object", + "description": "An object to hold reusable Operation Reply Address Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationReplyAddress.json" + } + ] + } + } + }, + "serverBindings": { + "type": "object", + "description": "An object to hold reusable Server Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/serverBindingsObject.json" + } + ] + } + } + }, + "channelBindings": { + "type": "object", + "description": "An object to hold reusable Channel Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/channelBindingsObject.json" + } + ] + } + } + }, + "operationBindings": { + "type": "object", + "description": "An object to hold reusable Operation Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/operationBindingsObject.json" + } + ] + } + } + }, + "messageBindings": { + "type": "object", + "description": "An object to hold reusable Message Bindings Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/messageBindingsObject.json" + } + ] + } + } + }, + "tags": { + "type": "object", + "description": "An object to hold reusable Tag Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/tag.json" + } + ] + } + } + }, + "externalDocs": { + "type": "object", + "description": "An object to hold reusable External Documentation Objects.", + "patternProperties": { + "^[\\w\\d\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "http://asyncapi.com/definitions/3.0.0/Reference.json" + }, + { + "$ref": "http://asyncapi.com/definitions/3.0.0/externalDocs.json" + } + ] + } + } + } + }, + "examples": [ + { + "components": { + "schemas": { + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + } + }, + "AvroExample": { + "schemaFormat": "application/vnd.apache.avro+json;version=1.9.0", + "schema": { + "$ref": "path/to/user-create.avsc#/UserCreate" + } + } + }, + "servers": { + "development": { + "host": "{stage}.in.mycompany.com:{port}", + "description": "RabbitMQ broker", + "protocol": "amqp", + "protocolVersion": "0-9-1", + "variables": { + "stage": { + "$ref": "#/components/serverVariables/stage" + }, + "port": { + "$ref": "#/components/serverVariables/port" + } + } + } + }, + "serverVariables": { + "stage": { + "default": "demo", + "description": "This value is assigned by the service provider, in this example `mycompany.com`" + }, + "port": { + "enum": [ + "5671", + "5672" + ], + "default": "5672" + } + }, + "channels": { + "user/signedup": { + "subscribe": { + "message": { + "$ref": "#/components/messages/userSignUp" + } + } + } + }, + "messages": { + "userSignUp": { + "summary": "Action to sign a user up.", + "description": "Multiline description of what this action does.\nHere you have another line.\n", + "tags": [ + { + "name": "user" + }, + { + "name": "signup" + } + ], + "headers": { + "type": "object", + "properties": { + "applicationInstanceId": { + "description": "Unique identifier for a given instance of the publishing application", + "type": "string" + } + } + }, + "payload": { + "type": "object", + "properties": { + "user": { + "$ref": "#/components/schemas/userCreate" + }, + "signup": { + "$ref": "#/components/schemas/signup" + } + } + } + } + }, + "parameters": { + "userId": { + "description": "Id of the user." + } + }, + "correlationIds": { + "default": { + "description": "Default Correlation ID", + "location": "$message.header#/correlationId" + } + }, + "messageTraits": { + "commonHeaders": { + "headers": { + "type": "object", + "properties": { + "my-app-header": { + "type": "integer", + "minimum": 0, + "maximum": 100 + } + } + } + } + } + } + } + ] + } + }, + "description": "!!Auto generated!! \n Do not manually edit. " +} diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json new file mode 100644 index 0000000000..23e293bfd9 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json @@ -0,0 +1,259 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "asyncapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "asyncapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "asyncapi" + }, + "kind": + { + "enum": [ "server", "client", "proxy" ] + }, + "options": + { + "properties": + { + "specs": + { + "title": "Specifications", + "type": "object", + "apiId": + { + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 + } + }, + "tcp": "#/$defs/binding/tcp/options", + "tls": "#/$defs/binding/tls/options", + "http": + { + "title": "Http", + "type": "object", + "properties": + { + "authorization": "$defs/options/binding/http/authorization" + }, + "additionalProperties": false + }, + "kafka": + { + "title": "Kafka", + "type": "object", + "properties": + { + "sasl": "$defs/options/binding/kafka/sasl" + }, + "additionalProperties": false + }, + "mqtt_kafka": + { + "title": "MQTT-Kafka", + "type": "object", + "properties": + { + "channels": + { + "title": "Channels", + "type": "object", + "properties": + { + "sessions": + { + "title": "Kafka Sessions Channel", + "type": "string" + }, + "messages": + { + "title": "Kafka Messages Channel", + "type": "string" + }, + "retained": + { + "title": "Kafka Retained Channel", + "type": "string" + }, + "additionalProperties": false + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "routes": + { + "items": + { + "properties": + { + "when": + { + "items": + { + "additionalProperties": false, + "properties": + { + "api-id": + { + "title": "MQTT API Id", + "type": "string" + }, + "operation-id": + { + "title": "MQTT Operation Id", + "type": "string" + } + } + } + }, + "with": + { + "properties": + { + "api-id": + { + "title": "Kafka API Id", + "type": "string" + }, + "operation-id": + { + "title": "Kafka Operation Id", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "required": + [ + "with" + ] + } + } + }, + "oneOf": + [ + { + "properties": + { + "kind": + { + "const": "server" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, + { + "properties": + { + "kind": + { + "const": "proxy" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, + { + "properties": + { + "kind": + { + "const": "client" + }, + "routes": + { + "items": + { + "properties": + { + "exit": false + } + } + }, + "exit": false + } + } + ] + } + } + } +] diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt new file mode 100644 index 0000000000..6e0038ff3d --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/client.rpt @@ -0,0 +1,51 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(759838734) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()) + .build()} +connected + +write "{\"test\"}" +write close + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()) + .build()} + +read "{\"message\": \"string\"}" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt new file mode 100644 index 0000000000..92f26191d2 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/http/create.pet/server.rpt @@ -0,0 +1,54 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(759838734) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()) + .build()} +connected + +read "{\"test\"}" +read closed + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .operationId("createPet") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()) + .build()} + +write "{\"message\": \"string\"}" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt new file mode 100644 index 0000000000..dbd8c02087 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(0, 1) + .build() + .build()} +write "Hello, world" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt new file mode 100644 index 0000000000..e725f5ad6e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/kafka/produce.message/server.rpt @@ -0,0 +1,44 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(0, 1) + .build() + .build()} +read "Hello, world" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt new file mode 100644 index 0000000000..9c04a2d76f --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/client.rpt @@ -0,0 +1,95 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +read zilla:data.empty + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +write "asyncapiMessage" +write flush + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt new file mode 100644 index 0000000000..a30cd06c20 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/mqtt/publish.and.subscribe/server.rpt @@ -0,0 +1,94 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +write zilla:data.empty +write flush + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +read "asyncapiMessage" + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt new file mode 100644 index 0000000000..dbd8c02087 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(0, 1) + .build() + .build()} +write "Hello, world" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt new file mode 100644 index 0000000000..e725f5ad6e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/produce.message/server.rpt @@ -0,0 +1,44 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(0, 1) + .build() + .build()} +read "Hello, world" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt new file mode 100644 index 0000000000..4c4c5da8b1 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/client.rpt @@ -0,0 +1,62 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi_kafka0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(810158698) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("sensors") + .partition(-1, -2) + .ackMode("NONE") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .deferred(0) + .partition(-1, -1) + .key("sensors/one") + .header("zilla:filter", "sensors") + .header("zilla:filter", "one") + .header("zilla:local", "client") + .headerInt("zilla:expiry", 15) + .header("zilla:content-type", "message") + .header("zilla:format", "TEXT") + .header("zilla:reply-to", "sensors") + .header("zilla:reply-key", "sensors/one") + .header("zilla:reply-filter", "sensors") + .header("zilla:reply-filter", "one") + .header("zilla:correlation-id", "info") + .header("zilla:qos", "0") + .build() + .build()} +write "message" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt new file mode 100644 index 0000000000..d8954e59c0 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.kafka.publish/server.rpt @@ -0,0 +1,60 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi_kafka0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(810158698) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("sensors") + .partition(-1, -2) + .ackMode("NONE") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .deferred(0) + .partition(-1, -1) + .key("sensors/one") + .header("zilla:filter", "sensors") + .header("zilla:filter", "one") + .header("zilla:local", "client") + .headerInt("zilla:expiry", 15) + .header("zilla:content-type", "message") + .header("zilla:format", "TEXT") + .header("zilla:reply-to", "sensors") + .header("zilla:reply-key", "sensors/one") + .header("zilla:reply-filter", "sensors") + .header("zilla:reply-filter", "one") + .header("zilla:correlation-id", "info") + .header("zilla:qos", "0") + .build() + .build()} +read "message" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt new file mode 100644 index 0000000000..a617fcde59 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi_proxy0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensors/one") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("message") + .format("TEXT") + .responseTopic("sensors/one") + .correlation("info") + .build() + .build()} +write "message" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt new file mode 100644 index 0000000000..0b74c3634e --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/proxy.mqtt.publish/server.rpt @@ -0,0 +1,48 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi_proxy0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(3724230511) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensors/one") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("message") + .format("TEXT") + .responseTopic("sensors/one") + .correlation("info") + .build() + .build()} +read "message" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt new file mode 100644 index 0000000000..7e16896b71 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/client.rpt @@ -0,0 +1,92 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +read zilla:data.empty + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +write "asyncapiMessage" +write flush + + +connect "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt new file mode 100644 index 0000000000..6155cf6cb4 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/publish.and.subscribe/server.rpt @@ -0,0 +1,91 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/asyncapi0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()) + .build()} + +connected + +write zilla:data.empty +write flush + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()) + .build()} + +connected + +read zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} +read "asyncapiMessage" + + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .extension(mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()) + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt new file mode 100644 index 0000000000..519bc10f7d --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/client.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()} +connected + +write "{\"test\"}" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()} + +read "{\"message\": \"string\"}" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt new file mode 100644 index 0000000000..3f504e0c61 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/http/create.pet/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${http:matchBeginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "8") + .build()} +connected + +read "{\"test\"}" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "21") + .build()} + +write "{\"message\": \"string\"}" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt new file mode 100644 index 0000000000..1e13239fe6 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/client.rpt @@ -0,0 +1,45 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/kafka" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(0, 1) + .build() + .build()} +write "Hello, world" +write flush diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt new file mode 100644 index 0000000000..ea9e2d6bb9 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/kafka/produce.message/server.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 16 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:matchBeginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("test") + .ackMode("LEADER_ONLY") + .build() + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(0, 1) + .build() + .build()} +read "Hello, world" diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt new file mode 100644 index 0000000000..c0de30bc54 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/client.rpt @@ -0,0 +1,88 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "duplex" + option zilla:ephemeral "test:composite0/mqtt" + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +connected + +read zilla:data.empty +read notify RECEIVED_SESSION_STATE + + +connect await RECEIVED_SESSION_STATE + "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "duplex" + option zilla:ephemeral "test:composite0/mqtt" + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()} + +connected + +write zilla:data.ext ${mqtt:dataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} + +write "asyncapiMessage" +write flush + + +connect await RECEIVED_SESSION_STATE + "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "duplex" + option zilla:ephemeral "test:composite0/mqtt" + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt new file mode 100644 index 0000000000..dc22830300 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt/publish.and.subscribe/server.rpt @@ -0,0 +1,82 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +write zilla:begin.ext ${mqtt:beginEx() + .typeId(zilla:id("mqtt")) + .session() + .clientId("client") + .build() + .build()} + +connected + +write zilla:data.empty +write flush + + +accepted + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .publish() + .clientId("client") + .topic("sensor/one") + .build() + .build()} + +connected + +read zilla:data.ext ${mqtt:matchDataEx() + .typeId(zilla:id("mqtt")) + .publish() + .qos("AT_MOST_ONCE") + .expiryInterval(15) + .contentType("asyncapiMessage") + .format("TEXT") + .responseTopic("sensor/one") + .correlation("info") + .build() + .build()} + +read "asyncapiMessage" + + +accepted + +read zilla:begin.ext ${mqtt:matchBeginEx() + .typeId(zilla:id("mqtt")) + .subscribe() + .clientId("client") + .filter("sensor/two", 1, "AT_MOST_ONCE", "SEND_RETAINED") + .build() + .build()} + +connected diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java new file mode 100644 index 0000000000..44ab1567fd --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/AsyncapiFunctionsTest.java @@ -0,0 +1,104 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.nio.ByteBuffer; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; +import org.kaazing.k3po.lang.el.BytesMatcher; + +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.specs.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; + +public class AsyncapiFunctionsTest +{ + @Test + public void shouldGetMapper() + { + AsyncapiFunctions.Mapper mapper = new AsyncapiFunctions.Mapper(); + assertEquals("asyncapi", mapper.getPrefixName()); + } + + @Test + public void shouldEncodeAsyncapiBeginExt() + { + final byte[] array = AsyncapiFunctions.beginEx() + .typeId(0) + .apiId(1) + .operationId("operationId") + .extension(new byte[] {1}) + .build(); + + DirectBuffer buffer = new UnsafeBuffer(array); + AsyncapiBeginExFW asyncapiBeginEx = new AsyncapiBeginExFW().wrap(buffer, 0, buffer.capacity()); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + assertEquals(1, asyncapiBeginEx.apiId()); + assertEquals("operationId", asyncapiBeginEx.operationId().asString()); + assertEquals(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build(), + asyncapiBeginEx.extension()); + } + + @Test + public void shouldMatchAsyncapiBeginExtensionOnly() throws Exception + { + BytesMatcher matcher = AsyncapiFunctions.matchBeginEx() + .typeId(0x00) + .apiId(1L) + .extension(new byte[] {1}) + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(15); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x00) + .apiId(1L) + .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } + + @Test + public void shouldMatchAsyncapiBeginExtension() throws Exception + { + BytesMatcher matcher = AsyncapiFunctions.matchBeginEx() + .typeId(0x00) + .apiId(1L) + .operationId("operationId") + .extension(new byte[] {1}) + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(26); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + new AsyncapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java new file mode 100644 index 0000000000..b7b6a8bf6d --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/config/SchemaTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/asyncapi/schema/asyncapi.schema.patch.json") + .schemaPatch("io/aklivity/zilla/specs/engine/schema/vault/test.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config"); + + @Test + public void shouldValidateMqttClient() + { + JsonObject config = schema.validate("client.mqtt.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateMqttSecureClient() + { + JsonObject config = schema.validate("client.mqtt.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateMqttServer() + { + JsonObject config = schema.validate("server.mqtt.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateMqttSecureServer() + { + JsonObject config = schema.validate("server.mqtt.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpClient() + { + JsonObject config = schema.validate("client.http.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpSecureClient() + { + JsonObject config = schema.validate("client.http.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpServer() + { + JsonObject config = schema.validate("server.http.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateHttpSecureServer() + { + JsonObject config = schema.validate("server.http.secure.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateKafkaClient() + { + JsonObject config = schema.validate("client.kafka.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateKafkaClientSasl() + { + JsonObject config = schema.validate("client.kafka.sasl.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateAsyncapiProxy() + { + JsonObject config = schema.validate("proxy.mqtt.kafka.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java new file mode 100644 index 0000000000..a12163b237 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/HttpIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java new file mode 100644 index 0000000000..9e43d077af --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/KafkaIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class KafkaIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("kafka", "io/aklivity/zilla/specs/binding/asyncapi/streams/kafka"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${kafka}/produce.message/client", + "${kafka}/produce.message/server" + }) + public void shouldProduceMessage() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/MqttIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/MqttIT.java new file mode 100644 index 0000000000..51ac9a8bec --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/MqttIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class MqttIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${mqtt}/publish.and.subscribe/client", + "${mqtt}/publish.and.subscribe/server" + }) + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java new file mode 100644 index 0000000000..77b4cbaf38 --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi/AsyncapiIT.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams.asyncapi; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${asyncapi}/mqtt/publish.and.subscribe/client", + "${asyncapi}/mqtt/publish.and.subscribe/server" + }) + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${asyncapi}/http/create.pet/client", + "${asyncapi}/http/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${asyncapi}/kafka/produce.message/client", + "${asyncapi}/kafka/produce.message/server" + }) + public void shouldProduceMessage() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${asyncapi}/proxy.kafka.publish/client", + "${asyncapi}/proxy.kafka.publish/server" + }) + public void shouldProxyPublishMessageKafka() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${asyncapi}/proxy.mqtt.publish/client", + "${asyncapi}/proxy.mqtt.publish/server" + }) + public void shouldProxyPublishMessageMqtt() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java new file mode 100644 index 0000000000..ccddb26bbd --- /dev/null +++ b/incubator/binding-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/asyncapi/streams/http/HttpIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.asyncapi.streams.http; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi/COPYRIGHT b/incubator/binding-asyncapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-asyncapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-asyncapi/LICENSE b/incubator/binding-asyncapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-asyncapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-asyncapi/NOTICE b/incubator/binding-asyncapi/NOTICE new file mode 100644 index 0000000000..17478992e3 --- /dev/null +++ b/incubator/binding-asyncapi/NOTICE @@ -0,0 +1,18 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jackson-dataformat-YAML under The Apache Software License, Version 2.0 + SnakeYAML under Apache License, Version 2.0 + diff --git a/incubator/binding-asyncapi/NOTICE.template b/incubator/binding-asyncapi/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/binding-asyncapi/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/binding-asyncapi/mvnw b/incubator/binding-asyncapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-asyncapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-asyncapi/mvnw.cmd b/incubator/binding-asyncapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-asyncapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-asyncapi/pom.xml b/incubator/binding-asyncapi/pom.xml new file mode 100644 index 0000000000..b7b34f4780 --- /dev/null +++ b/incubator/binding-asyncapi/pom.xml @@ -0,0 +1,342 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + binding-asyncapi + zilla::incubator::binding-asyncapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-asyncapi.spec + ${project.version} + provided + + + ${project.groupId} + engine.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-mqtt + ${project.version} + provided + + + io.aklivity.zilla + binding-http + ${project.version} + provided + + + io.aklivity.zilla + binding-kafka + ${project.version} + provided + + + io.aklivity.zilla + binding-tcp + ${project.version} + provided + + + io.aklivity.zilla + binding-mqtt-kafka + ${project.version} + provided + + + io.aklivity.zilla + binding-tls + ${project.version} + provided + + + io.aklivity.zilla + catalog-inline + ${project.version} + provided + + + io.aklivity.zilla + model-core + ${project.version} + provided + + + io.aklivity.zilla + model-json + ${project.version} + provided + + + com.fasterxml.jackson.dataformat + jackson-dataformat-yaml + 2.16.1 + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + io.aklivity.zilla + binding-mqtt-kafka + test-jar + ${project.version} + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core mqtt http kafka asyncapi + io.aklivity.zilla.runtime.binding.asyncapi.internal.types + + + + + generate + + + + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-asyncapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/asyncapi/\E + io/aklivity/zilla/runtime/binding/asyncapi/internal/ + + + + + io/aklivity/zilla/specs/binding/asyncapi/schema/*.json + ${project.build.directory}/classes + + + + extract-files + process-resources + + unpack + + + + + ${project.groupId} + binding-asyncapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.jasig.maven + maven-notice-plugin + + + com.mycila + license-maven-plugin + + + src/test/resources/io/aklivity/zilla/runtime/binding/asyncapi/internal/**/* + + + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/asyncapi/internal/types/**/*.class + io/aklivity/zilla/runtime/binding/asyncapi/internal/model/*.class + io/aklivity/zilla/runtime/binding/asyncapi/internal/model2/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java new file mode 100644 index 0000000000..4b80d6f882 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfig.java @@ -0,0 +1,46 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +public class AsyncapiChannelsConfig +{ + public final String sessions; + public final String messages; + public final String retained; + + public static AsyncapiChannelsConfigBuilder builder() + { + return new AsyncapiChannelsConfigBuilder<>(AsyncapiChannelsConfig.class::cast); + } + + public static AsyncapiChannelsConfigBuilder builder( + Function mapper) + { + return new AsyncapiChannelsConfigBuilder<>(mapper); + } + + public AsyncapiChannelsConfig( + String sessions, + String messages, + String retained) + { + this.sessions = sessions; + this.messages = messages; + this.retained = retained; + } +} + diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java new file mode 100644 index 0000000000..815d38ab62 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiChannelsConfigBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +public class AsyncapiChannelsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private String sessions; + private String messages; + private String retained; + AsyncapiChannelsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + public AsyncapiChannelsConfigBuilder sessions( + String sessions) + { + this.sessions = sessions; + return this; + } + + public AsyncapiChannelsConfigBuilder messages( + String messages) + { + this.messages = messages; + return this; + } + + public AsyncapiChannelsConfigBuilder retained( + String retained) + { + this.retained = retained; + return this; + } + + + @Override + public T build() + { + return mapper.apply( + new AsyncapiChannelsConfig(sessions, messages, retained)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java new file mode 100644 index 0000000000..628692248a --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; + +public class AsyncapiConfig +{ + public final String apiLabel; + public final long apiId; + public final String location; + public final Asyncapi asyncapi; + + public AsyncapiConfig( + String apiLabel, + long apiId, + String location, + Asyncapi asyncapi) + { + this.apiLabel = apiLabel; + this.apiId = apiId; + this.location = location; + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java new file mode 100644 index 0000000000..a4e5190e8c --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +public class AsyncapiMqttKafkaConfig +{ + public final AsyncapiChannelsConfig channels; + + public static AsyncapiMqttKafkaConfigBuilder builder() + { + return new AsyncapiMqttKafkaConfigBuilder<>(AsyncapiMqttKafkaConfig.class::cast); + } + + public static AsyncapiMqttKafkaConfigBuilder builder( + Function mapper) + { + return new AsyncapiMqttKafkaConfigBuilder<>(mapper); + } + + public AsyncapiMqttKafkaConfig( + AsyncapiChannelsConfig channels) + { + this.channels = channels; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java new file mode 100644 index 0000000000..31c2566380 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiMqttKafkaConfigBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class AsyncapiMqttKafkaConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private AsyncapiChannelsConfig channels; + + AsyncapiMqttKafkaConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public AsyncapiMqttKafkaConfigBuilder channels( + AsyncapiChannelsConfig channels) + { + this.channels = channels; + return this; + } + + @Override + public T build() + { + return mapper.apply(new AsyncapiMqttKafkaConfig(channels)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java new file mode 100644 index 0000000000..105f83e5d5 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfig.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class AsyncapiOptionsConfig extends OptionsConfig +{ + public final List specs; + public final TcpOptionsConfig tcp; + public final TlsOptionsConfig tls; + public final HttpOptionsConfig http; + public final KafkaOptionsConfig kafka; + public final AsyncapiMqttKafkaConfig mqttKafka; + + public static AsyncapiOptionsConfigBuilder builder() + { + return new AsyncapiOptionsConfigBuilder<>(AsyncapiOptionsConfig.class::cast); + } + + public static AsyncapiOptionsConfigBuilder builder( + Function mapper) + { + return new AsyncapiOptionsConfigBuilder<>(mapper); + } + + public AsyncapiOptionsConfig( + List specs, + TcpOptionsConfig tcp, + TlsOptionsConfig tls, + HttpOptionsConfig http, + KafkaOptionsConfig kafka, + AsyncapiMqttKafkaConfig mqttKafka) + { + this.specs = specs; + this.http = http; + this.tcp = tcp; + this.tls = tls; + this.kafka = kafka; + this.mqttKafka = mqttKafka; + } + + public long resolveApiId( + String apiLabel) + { + long apiId = -1; + for (AsyncapiConfig c : specs) + { + if (c.apiLabel.equals(apiLabel)) + { + apiId = c.apiId; + break; + } + } + return apiId; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java new file mode 100644 index 0000000000..06d9a852e0 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiOptionsConfigBuilder.java @@ -0,0 +1,110 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class AsyncapiOptionsConfigBuilder extends ConfigBuilder> +{ + private static final String DEFAULT_SESSIONS_CHANNEL = "mqttSessions"; + private static final String DEFAULT_RETAINED_CHANNEL = "mqttRetained"; + private static final String DEFAULT_MESSAGES_CHANNEL = "mqttMessages"; + private static final AsyncapiMqttKafkaConfig DEFAULT_MQTT_KAFKA = + AsyncapiMqttKafkaConfig.builder() + .channels(AsyncapiChannelsConfig.builder() + .sessions(DEFAULT_SESSIONS_CHANNEL) + .messages(DEFAULT_MESSAGES_CHANNEL) + .retained(DEFAULT_RETAINED_CHANNEL) + .build()) + .build(); + + private final Function mapper; + + public List specs; + private TcpOptionsConfig tcp; + private TlsOptionsConfig tls; + private HttpOptionsConfig http; + private KafkaOptionsConfig kafka; + private AsyncapiMqttKafkaConfig mqttKafka = DEFAULT_MQTT_KAFKA; + + AsyncapiOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public AsyncapiOptionsConfigBuilder specs( + List specs) + { + this.specs = specs; + return this; + } + + public AsyncapiOptionsConfigBuilder tcp( + TcpOptionsConfig tcp) + { + this.tcp = tcp; + return this; + } + + public AsyncapiOptionsConfigBuilder tls( + TlsOptionsConfig tls) + { + this.tls = tls; + return this; + } + + public AsyncapiOptionsConfigBuilder http( + HttpOptionsConfig http) + { + this.http = http; + return this; + } + + public AsyncapiOptionsConfigBuilder kafka( + KafkaOptionsConfig kafka) + { + this.kafka = kafka; + return this; + } + + public AsyncapiOptionsConfigBuilder mqttKafka( + AsyncapiMqttKafkaConfig mqttKafka) + { + this.mqttKafka = mqttKafka; + return this; + } + + @Override + public T build() + { + return mapper.apply(new AsyncapiOptionsConfig(specs, tcp, tls, http, kafka, mqttKafka)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java new file mode 100644 index 0000000000..403b966819 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/config/AsyncapiParser.java @@ -0,0 +1,132 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.config; + +import static java.util.Collections.unmodifiableMap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.engine.config.ConfigException; + +public class AsyncapiParser +{ + private final Map schemas; + + public AsyncapiParser() + { + Map schemas = new Object2ObjectHashMap<>(); + schemas.put("2.6.0", schema("2.6.0")); + schemas.put("3.0.0", schema("3.0.0")); + this.schemas = unmodifiableMap(schemas); + } + + public Asyncapi parse( + String asyncapiText) + { + Asyncapi asyncapi = null; + + List errors = new LinkedList<>(); + + try + { + String asyncApiVersion = detectAsyncApiVersion(asyncapiText); + + JsonValidationService service = JsonValidationService.newInstance(); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + JsonSchema schema = schemas.get(asyncApiVersion); + + service.createReader(new StringReader(asyncapiText), schema, handler).read(); + + Jsonb jsonb = JsonbBuilder.create(); + + asyncapi = jsonb.fromJson(asyncapiText, Asyncapi.class); + } + catch (Exception ex) + { + errors.add(ex); + } + + if (!errors.isEmpty()) + { + Exception ex = errors.remove(0); + errors.forEach(ex::addSuppressed); + rethrowUnchecked(ex); + } + + return asyncapi; + } + + private JsonSchema schema( + String version) + { + InputStream schemaInput = null; + + if (version.startsWith("2.6")) + { + schemaInput = AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.2.6.schema.json"); + } + else if (version.startsWith("3.0")) + { + schemaInput = AsyncapiBinding.class.getResourceAsStream("schema/asyncapi.3.0.schema.json"); + } + + JsonValidationService service = JsonValidationService.newInstance(); + + return service.createSchemaReaderFactoryBuilder() + .withSpecVersionDetection(true) + .build() + .createSchemaReader(schemaInput) + .read(); + } + + private String detectAsyncApiVersion( + String openapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(openapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("asyncapi")) + { + return json.getString("asyncapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine AsyncApi version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading AsyncApi document.", e); + } + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java new file mode 100644 index 0000000000..45ed67c811 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class AsyncapiBinding implements Binding +{ + public static final String NAME = "asyncapi"; + + private final AsyncapiConfiguration config; + + AsyncapiBinding( + AsyncapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return AsyncapiBinding.NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/asyncapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public AsyncapiBindingContext supply( + EngineContext context) + { + return new AsyncapiBindingContext(config, context); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java new file mode 100644 index 0000000000..8f4af3e8fa --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingAdapter.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public class AsyncapiBindingAdapter implements CompositeBindingAdapterSpi +{ + private final Map> composites; + + public AsyncapiBindingAdapter() + { + Map> composites = new EnumMap<>(KindConfig.class); + composites.put(SERVER, new AsyncapiServerCompositeBindingAdapter()::adapt); + composites.put(CLIENT, new AsyncapiClientCompositeBindingAdapter()::adapt); + composites.put(PROXY, new AsyncapiProxyCompositeBindingAdapter()::adapt); + this.composites = composites; + } + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + return composites.get(binding.kind).apply(binding); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java new file mode 100644 index 0000000000..d97c23edb7 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiClientFactory; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiProxyFactory; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiServerFactory; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.AsyncapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class AsyncapiBindingContext implements BindingContext +{ + private final Map factories; + + AsyncapiBindingContext( + AsyncapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(SERVER, new AsyncapiServerFactory(config, context)); + factories.put(CLIENT, new AsyncapiClientFactory(config, context)); + factories.put(PROXY, new AsyncapiProxyFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + AsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + AsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java new file mode 100644 index 0000000000..e55fb89fd0 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class AsyncapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public AsyncapiBinding create( + Configuration config) + { + return new AsyncapiBinding(new AsyncapiConfiguration(config)); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java new file mode 100644 index 0000000000..2beccbbd22 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiClientCompositeBindingAdapter.java @@ -0,0 +1,90 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; + +public class AsyncapiClientCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; + AsyncapiConfig asyncapiConfig = options.specs.get(0); + this.asyncapi = asyncapiConfig.asyncapi; + + //TODO: add composite for all servers + AsyncapiServerView firstServer = AsyncapiServerView.of(asyncapi.servers.entrySet().iterator().next().getValue()); + this.qname = binding.qname; + this.qvault = binding.qvault; + this.protocol = resolveProtocol(firstServer.protocol(), options); + this.isTlsEnabled = protocol.isSecure(); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s.%s", qname, "$composite")) + .inject(n -> this.injectCatalog(n, asyncapi)) + .inject(protocol::injectProtocolClientCache) + .binding() + .name(String.format("%s_client0", protocol.scheme)) + .type(protocol.scheme) + .kind(CLIENT) + .inject(protocol::injectProtocolClientOptions) + .exit(isTlsEnabled ? "tls_client0" : "tcp_client0") + .build() + .inject(n -> injectTlsClient(n, options)) + .binding() + .name("tcp_client0") + .type("tcp") + .kind(CLIENT) + .options(options.tcp) + .build() + .build() + .build(); + } + + private NamespaceConfigBuilder injectTlsClient( + NamespaceConfigBuilder namespace, + AsyncapiOptionsConfig options) + { + if (isTlsEnabled) + { + namespace + .binding() + .name("tls_client0") + .type("tls") + .kind(CLIENT) + .options(options.tls) + .vault(qvault) + .exit("tcp_client0") + .build(); + } + return namespace; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java new file mode 100644 index 0000000000..127a2afbba --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiCompositeBindingAdapter.java @@ -0,0 +1,151 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.MINIMIZE_QUOTES; +import static com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature.WRITE_DOC_START_MARKER; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLMapper; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiSchemaView; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineSchemaConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; + +public class AsyncapiCompositeBindingAdapter +{ + protected static final String INLINE_CATALOG_NAME = "catalog0"; + protected static final String INLINE_CATALOG_TYPE = "inline"; + protected static final String VERSION_LATEST = "latest"; + protected static final String APPLICATION_JSON = "application/json"; + + protected Asyncapi asyncapi; + protected Map asyncApis; + protected boolean isTlsEnabled; + protected AsyncapiProtocol protocol; + protected String qname; + protected String qvault; + + + protected AsyncapiProtocol resolveProtocol( + String protocolName, + AsyncapiOptionsConfig options) + { + Pattern pattern = Pattern.compile("(http|mqtt|kafka)"); + Matcher matcher = pattern.matcher(protocolName); + AsyncapiProtocol protocol = null; + if (matcher.find()) + { + switch (matcher.group()) + { + case "http": + protocol = new AsyncapiHttpProtocol(qname, asyncapi, options); + break; + case "mqtt": + protocol = new AyncapiMqttProtocol(qname, asyncapi); + break; + case "kafka": + case "kafka-secure": + protocol = new AyncapiKafkaProtocol(qname, asyncapi, options, protocolName); + break; + } + } + else + { + // TODO: should we do something? + } + return protocol; + } + + protected NamespaceConfigBuilder injectCatalog( + NamespaceConfigBuilder namespace, + Asyncapi asyncapi) + { + if (asyncapi.components != null && asyncapi.components.schemas != null && !asyncapi.components.schemas.isEmpty()) + { + namespace + .catalog() + .name(INLINE_CATALOG_NAME) + .type(INLINE_CATALOG_TYPE) + .options(InlineOptionsConfig::builder) + .subjects() + .inject(this::injectSubjects) + .build() + .build() + .build(); + + } + return namespace; + } + + protected InlineSchemaConfigBuilder injectSubjects( + InlineSchemaConfigBuilder subjects) + { + try (Jsonb jsonb = JsonbBuilder.create()) + { + YAMLMapper yaml = YAMLMapper.builder() + .disable(WRITE_DOC_START_MARKER) + .enable(MINIMIZE_QUOTES) + .build(); + for (Map.Entry entry : asyncapi.components.schemas.entrySet()) + { + AsyncapiSchemaView schema = AsyncapiSchemaView.of(asyncapi.components.schemas, entry.getValue()); + subjects + .subject(entry.getKey()) + .version(VERSION_LATEST) + .schema(writeSchemaYaml(jsonb, yaml, schema)) + .build(); + } + } + catch (Exception ex) + { + rethrowUnchecked(ex); + } + return subjects; + } + + protected static String writeSchemaYaml( + Jsonb jsonb, + YAMLMapper yaml, + Object schema) + { + String result = null; + try + { + String schemaJson = jsonb.toJson(schema); + JsonNode json = new ObjectMapper().readTree(schemaJson); + result = yaml.writeValueAsString(json); + } + catch (JsonProcessingException ex) + { + rethrowUnchecked(ex); + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java new file mode 100644 index 0000000000..bf602321da --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class AsyncapiConfiguration extends Configuration +{ + public static final LongPropertyDef ASYNCAPI_TARGET_ROUTE_ID; + private static final ConfigurationDef ASYNCAPI_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.asyncapi"); + ASYNCAPI_TARGET_ROUTE_ID = config.property("target.route.id", -1L); + ASYNCAPI_CONFIG = config; + } + + public AsyncapiConfiguration( + Configuration config) + { + super(ASYNCAPI_CONFIG, config); + } + + public long targetRouteId() + { + return ASYNCAPI_TARGET_ROUTE_ID.getAsLong(this); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java new file mode 100644 index 0000000000..bbcc1757ec --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiHttpProtocol.java @@ -0,0 +1,301 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN; +import static java.util.Objects.requireNonNull; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiItem; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiParameter; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiServer; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.binding.http.config.HttpAuthorizationConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpConditionConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig.Method; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.GuardedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AsyncapiHttpProtocol extends AsyncapiProtocol +{ + private static final Map MODELS = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + private static final String SCHEME = "http"; + private static final String SECURE_SCHEME = "https"; + private final Map securitySchemes; + private final String authorizationHeader; + private final boolean isJwtEnabled; + private final String guardName; + private final HttpAuthorizationConfig authorization; + + protected AsyncapiHttpProtocol( + String qname, + Asyncapi asyncApi, + AsyncapiOptionsConfig options) + { + super(qname, asyncApi, SCHEME, SECURE_SCHEME); + this.securitySchemes = resolveSecuritySchemes(); + this.authorizationHeader = resolveAuthorizationHeader(); + this.isJwtEnabled = !securitySchemes.isEmpty(); + final HttpOptionsConfig httpOptions = options.http; + this.guardName = httpOptions != null ? httpOptions.authorization.name : null; + this.authorization = httpOptions != null ? httpOptions.authorization : null; + } + + + @Override + public BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding) + { + return binding + .options(HttpOptionsConfig::builder) + .access() + .policy(CROSS_ORIGIN) + .build() + .inject(this::injectHttpServerOptions) + .inject(this::injectHttpServerRequests) + .build(); + } + + @Override + public BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding) + { + for (Map.Entry entry : asyncApi.servers.entrySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(entry.getValue()); + for (String name : asyncApi.operations.keySet()) + { + AsyncapiOperation operation = asyncApi.operations.get(name); + AsyncapiChannelView channel = AsyncapiChannelView.of(asyncApi.channels, operation.channel); + String path = channel.address().replaceAll("\\{[^}]+\\}", "*"); + String method = operation.bindings.get("http").method; + binding + .route() + .exit(qname) + .when(HttpConditionConfig::builder) + .header(":scheme", server.scheme()) + .header(":authority", server.authority()) + .header(":path", path) + .header(":method", method) + .build() + .inject(route -> injectHttpServerRouteGuarded(route, server)) + .build(); + } + } + return binding; + } + + @Override + protected boolean isSecure() + { + return findFirstServerUrlWithScheme(SECURE_SCHEME) != null; + } + + private HttpOptionsConfigBuilder injectHttpServerOptions( + HttpOptionsConfigBuilder options) + { + if (isJwtEnabled) + { + options.authorization(authorization).build(); + } + return options; + } + + private HttpOptionsConfigBuilder injectHttpServerRequests( + HttpOptionsConfigBuilder options) + { + for (String name : asyncApi.operations.keySet()) + { + AsyncapiOperation operation = asyncApi.operations.get(name); + AsyncapiChannelView channel = AsyncapiChannelView.of(asyncApi.channels, operation.channel); + String path = channel.address(); + Method method = Method.valueOf(operation.bindings.get("http").method); + if (channel.messages() != null && !channel.messages().isEmpty() || + channel.parameters() != null && !channel.parameters().isEmpty()) + { + options + .request() + .path(path) + .method(method) + .inject(request -> injectContent(request, channel.messages())) + .inject(request -> injectPathParams(request, channel.parameters())) + .build(); + } + } + return options; + } + + private HttpRequestConfigBuilder injectContent( + HttpRequestConfigBuilder request, + Map messages) + { + if (messages != null) + { + if (hasJsonContentType()) + { + request. + content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(catalog -> injectSchemas(catalog, messages)) + .build() + .build(); + } + } + return request; + } + + private CatalogedConfigBuilder injectSchemas( + CatalogedConfigBuilder catalog, + Map messages) + { + for (String name : messages.keySet()) + { + AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messages.get(name)); + String subject = message.refKey() != null ? message.refKey() : name; + catalog + .schema() + .subject(subject) + .build() + .build(); + } + return catalog; + } + + private HttpRequestConfigBuilder injectPathParams( + HttpRequestConfigBuilder request, + Map parameters) + { + if (parameters != null) + { + for (String name : parameters.keySet()) + { + AsyncapiParameter parameter = parameters.get(name); + if (parameter.schema != null && parameter.schema.type != null) + { + ModelConfig model = MODELS.get(parameter.schema.type); + if (model != null) + { + request + .pathParam() + .name(name) + .model(model) + .build(); + } + } + } + } + return request; + } + + private RouteConfigBuilder injectHttpServerRouteGuarded( + RouteConfigBuilder route, + AsyncapiServerView server) + { + if (server.security() != null) + { + for (Map> securityItem : server.security()) + { + for (String securityItemLabel : securityItem.keySet()) + { + if (isJwtEnabled && "jwt".equals(securitySchemes.get(securityItemLabel))) + { + route + .guarded() + .name(guardName) + .inject(guarded -> injectGuardedRoles(guarded, securityItem.get(securityItemLabel))) + .build(); + break; + } + } + } + } + return route; + } + + private GuardedConfigBuilder injectGuardedRoles( + GuardedConfigBuilder guarded, + List roles) + { + for (String role : roles) + { + guarded.role(role); + } + return guarded; + } + + private Map resolveSecuritySchemes() + { + requireNonNull(asyncApi); + Map result = new HashMap<>(); + if (asyncApi.components != null && asyncApi.components.securitySchemes != null) + { + for (String securitySchemeName : asyncApi.components.securitySchemes.keySet()) + { + String guardType = asyncApi.components.securitySchemes.get(securitySchemeName).bearerFormat; + if ("jwt".equals(guardType)) + { + result.put(securitySchemeName, guardType); + } + } + } + return result; + } + + private String resolveAuthorizationHeader() + { + requireNonNull(asyncApi); + requireNonNull(asyncApi.components); + String result = null; + if (asyncApi.components.messages != null) + { + for (Map.Entry entry : asyncApi.components.messages.entrySet()) + { + AsyncapiMessage message = entry.getValue(); + if (message.headers != null && message.headers.properties != null) + { + AsyncapiItem authorization = message.headers.properties.get("authorization"); + if (authorization != null) + { + result = authorization.description; + break; + } + } + } + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java new file mode 100644 index 0000000000..66ab35de82 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProtocol.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; + +public abstract class AsyncapiProtocol +{ + protected static final String INLINE_CATALOG_NAME = "catalog0"; + protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + protected static final String VERSION_LATEST = "latest"; + + protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + + protected Asyncapi asyncApi; + protected String qname; + public final String scheme; + public final String secureScheme; + + protected AsyncapiProtocol( + String qname, + Asyncapi asyncApi, + String scheme, + String secureScheme) + { + this.qname = qname; + this.asyncApi = asyncApi; + this.scheme = scheme; + this.secureScheme = secureScheme; + } + + public abstract BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding); + + public abstract BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding); + + public NamespaceConfigBuilder injectProtocolClientCache( + NamespaceConfigBuilder namespace) + { + return namespace; + } + + public BindingConfigBuilder injectProtocolClientOptions( + BindingConfigBuilder binding) + { + return binding; + } + + protected CatalogedConfigBuilder injectJsonSchemas( + CatalogedConfigBuilder cataloged, + Map messages, + String contentType) + { + for (Map.Entry messageEntry : messages.entrySet()) + { + AsyncapiMessageView message = + AsyncapiMessageView.of(asyncApi.components.messages, messageEntry.getValue()); + String schema = messageEntry.getKey(); + if (message.contentType().equals(contentType)) + { + cataloged + .schema() + .version(VERSION_LATEST) + .subject(schema) + .build() + .build(); + } + else + { + throw new RuntimeException("Invalid content type"); + } + } + return cataloged; + } + + protected boolean hasJsonContentType() + { + String contentType = null; + if (asyncApi.components != null && asyncApi.components.messages != null && + !asyncApi.components.messages.isEmpty()) + { + AsyncapiMessage firstAsyncapiMessage = asyncApi.components.messages.entrySet().stream() + .findFirst().get().getValue(); + contentType = AsyncapiMessageView.of(asyncApi.components.messages, firstAsyncapiMessage).contentType(); + } + return contentType != null && jsonContentType.reset(contentType).matches(); + } + + protected abstract boolean isSecure(); + + protected int[] resolvePorts() + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + protected URI findFirstServerUrlWithScheme( + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (String key : asyncApi.servers.keySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncApi.servers.get(key)); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java new file mode 100644 index 0000000000..c85e764503 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiProxyCompositeBindingAdapter.java @@ -0,0 +1,149 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiConditionConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; + +public class AsyncapiProxyCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String ASYNCAPI_SEND_ACTION_NAME = "send"; + private static final String ASYNCAPI_RECEIVE_ACTION_NAME = "receive"; + private static final String ASYNCAPI_KAFKA_PROTOCOL_NAME = "kafka"; + private static final String ASYNCAPI_MQTT_PROTOCOL_NAME = "mqtt"; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; + List routes = binding.routes.stream() + .map(r -> new AsyncapiRouteConfig(r, options::resolveApiId)) + .collect(Collectors.toList()); + this.asyncApis = options.specs.stream().collect(Collectors.toUnmodifiableMap(a -> a.apiLabel, a -> a.asyncapi)); + this.qname = binding.qname; + + String sessions = ""; + String messages = ""; + String retained = ""; + for (Asyncapi asyncapi : asyncApis.values()) + { + if (asyncapi.channels.containsKey(options.mqttKafka.channels.sessions)) + { + sessions = asyncapi.channels.get(options.mqttKafka.channels.sessions).address; + } + + if (asyncapi.channels.containsKey(options.mqttKafka.channels.messages)) + { + messages = asyncapi.channels.get(options.mqttKafka.channels.messages).address; + } + + if (asyncapi.channels.containsKey(options.mqttKafka.channels.retained)) + { + retained = asyncapi.channels.get(options.mqttKafka.channels.retained).address; + } + } + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/%s", qname, "mqtt-kafka")) + .binding() + .name("mqtt_kafka_proxy0") + .type("mqtt-kafka") + .kind(PROXY) + .options(MqttKafkaOptionsConfig::builder) + .topics() + .sessions(sessions) + .messages(messages) + .retained(retained) + .build() + .clients(Collections.emptyList()) + .build() + .inject(b -> this.injectMqttKafkaRoutes(b, routes)) + .build() + .build() + .build(); + } + + public BindingConfigBuilder injectMqttKafkaRoutes( + BindingConfigBuilder binding, + List routes) + { + inject: + for (AsyncapiRouteConfig route : routes) + { + final RouteConfigBuilder> routeBuilder = binding.route(); + + final Asyncapi kafkaAsyncapi = asyncApis.get(route.with.apiId); + + if (kafkaAsyncapi.servers.values().stream().anyMatch(s -> !s.protocol.startsWith(ASYNCAPI_KAFKA_PROTOCOL_NAME))) + { + break inject; + } + + final AsyncapiOperation withOperation = kafkaAsyncapi.operations.get(route.with.operationId); + final String messages = AsyncapiChannelView.of(kafkaAsyncapi.channels, withOperation.channel).address(); + + for (AsyncapiConditionConfig condition : route.when) + { + final Asyncapi mqttAsyncapi = asyncApis.get(condition.apiId); + if (mqttAsyncapi.servers.values().stream().anyMatch(s -> !s.protocol.startsWith(ASYNCAPI_MQTT_PROTOCOL_NAME))) + { + break inject; + } + final AsyncapiOperation whenOperation = mqttAsyncapi.operations.get(condition.operationId); + final AsyncapiChannelView channel = AsyncapiChannelView.of(mqttAsyncapi.channels, whenOperation.channel); + final MqttKafkaConditionKind kind = whenOperation.action.equals(ASYNCAPI_SEND_ACTION_NAME) ? + MqttKafkaConditionKind.PUBLISH : MqttKafkaConditionKind.SUBSCRIBE; + final String topic = channel.address().replaceAll("\\{[^}]+\\}", "+"); + routeBuilder + .when(MqttKafkaConditionConfig::builder) + .topic(topic) + .kind(kind) + .build() + .with(MqttKafkaWithConfig::builder) + .messages(messages) + .build() + .exit(qname); + } + binding = routeBuilder.build(); + } + return binding; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java new file mode 100644 index 0000000000..56712d6fdd --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiServerCompositeBindingAdapter.java @@ -0,0 +1,141 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiView; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; + +public class AsyncapiServerCompositeBindingAdapter extends AsyncapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private int[] compositePorts; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + AsyncapiOptionsConfig options = (AsyncapiOptionsConfig) binding.options; + AsyncapiConfig asyncapiConfig = options.specs.get(0); + this.asyncapi = asyncapiConfig.asyncapi; + AsyncapiView view = AsyncapiView.of(asyncapi); + + //TODO: add composite for all servers + AsyncapiServerView firstServer = AsyncapiServerView.of(asyncapi.servers.entrySet().iterator().next().getValue()); + + this.qname = binding.qname; + this.qvault = binding.qvault; + this.protocol = resolveProtocol(firstServer.protocol(), options); + int[] allPorts = view.resolveAllPorts(); + this.compositePorts = protocol.resolvePorts(); + this.isTlsEnabled = protocol.isSecure(); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/%s", qname, protocol.scheme)) + .inject(n -> this.injectCatalog(n, asyncapi)) + .binding() + .name("tcp_server0") + .type("tcp") + .kind(SERVER) + .options(TcpOptionsConfig::builder) + .host("0.0.0.0") + .ports(allPorts) + .build() + .inject(this::injectPlainTcpRoute) + .inject(this::injectTlsTcpRoute) + .build() + .inject(n -> injectTlsServer(n, options)) + .binding() + .name(String.format("%s_server0", protocol.scheme)) + .type(protocol.scheme) + .kind(SERVER) + .inject(protocol::injectProtocolServerOptions) + .inject(protocol::injectProtocolServerRoutes) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectPlainTcpRoute( + BindingConfigBuilder binding) + { + if (!isTlsEnabled) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(compositePorts) + .build() + .exit(String.format("%s_server0", protocol.scheme)) + .build(); + } + return binding; + } + + private BindingConfigBuilder injectTlsTcpRoute( + BindingConfigBuilder binding) + { + if (isTlsEnabled) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(compositePorts) + .build() + .exit("tls_server0") + .build(); + } + return binding; + } + + private NamespaceConfigBuilder injectTlsServer( + NamespaceConfigBuilder namespace, + AsyncapiOptionsConfig options) + { + if (isTlsEnabled) + { + namespace + .binding() + .name("tls_server0") + .type("tls") + .kind(SERVER) + .options(TlsOptionsConfig::builder) + .keys(options.tls.keys) + .sni(options.tls.sni) + .alpn(options.tls.alpn) + .build() + .vault(qvault) + .exit(String.format("%s_server0", protocol.scheme)) + .build(); + } + return namespace; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java new file mode 100644 index 0000000000..ec7e1243b2 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiKafkaProtocol.java @@ -0,0 +1,209 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import java.util.Map; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiMessageView; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiSchemaView; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AyncapiKafkaProtocol extends AsyncapiProtocol +{ + private static final String SCHEME = "kafka"; + private static final String SECURE_SCHEME = ""; + private static final String SECURE_PROTOCOL = "kafka-secure"; + private static final Pattern PARAMETERIZED_TOPIC_PATTERN = Pattern.compile("\\{.*?\\}"); + + private final String protocol; + private final KafkaSaslConfig sasl; + + public AyncapiKafkaProtocol( + String qname, + Asyncapi asyncApi, + AsyncapiOptionsConfig options, + String protocol) + { + super(qname, asyncApi, SCHEME, SECURE_SCHEME); + this.protocol = protocol; + this.sasl = options.kafka != null ? options.kafka.sasl : null; + } + + @Override + public NamespaceConfigBuilder injectProtocolClientCache( + NamespaceConfigBuilder namespace) + { + return namespace + .binding() + .name("kafka_cache_client0") + .type("kafka") + .kind(KindConfig.CACHE_CLIENT) + .options(KafkaOptionsConfig::builder) + .inject(this::injectKafkaTopicOptions) + .build() + .exit("kafka_cache_server0") + .build() + .binding() + .name("kafka_cache_server0") + .type("kafka") + .kind(KindConfig.CACHE_SERVER) + .options(KafkaOptionsConfig::builder) + .inject(this::injectKafkaBootstrapOptions) + .inject(this::injectKafkaTopicOptions) + .build() + .exit("kafka_client0") + .build(); + } + + @Override + public BindingConfigBuilder injectProtocolClientOptions( + BindingConfigBuilder binding) + { + return binding.options(KafkaOptionsConfig::builder) + .inject(this::injectKafkaSaslOptions) + .inject(this::injectKafkaServerOptions) + .build(); + } + + @Override + public BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding) + { + return binding; + } + + @Override + public BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding) + { + return binding; + } + + @Override + protected boolean isSecure() + { + return protocol.equals(SECURE_PROTOCOL); + } + + private KafkaOptionsConfigBuilder injectKafkaSaslOptions( + KafkaOptionsConfigBuilder options) + { + return sasl != null ? options.sasl(KafkaSaslConfig::builder) + .mechanism(sasl.mechanism) + .username(sasl.username) + .password(sasl.password) + .build() : options; + } + + private KafkaOptionsConfigBuilder injectKafkaServerOptions( + KafkaOptionsConfigBuilder options) + { + return options.servers(asyncApi.servers.values().stream().map(s -> + { + String[] hostAndPort = s.host.split(":"); + return KafkaServerConfig.builder() + .host(hostAndPort[0]) + .port(Integer.parseInt(hostAndPort[1])) + .build(); + }).collect(Collectors.toList())); + } + + private KafkaOptionsConfigBuilder injectKafkaTopicOptions( + KafkaOptionsConfigBuilder options) + { + for (String name : asyncApi.operations.keySet()) + { + AsyncapiOperation operation = asyncApi.operations.get(name); + AsyncapiChannelView channel = AsyncapiChannelView.of(asyncApi.channels, operation.channel); + String topic = channel.address(); + + if (channel.messages() != null && !channel.messages().isEmpty() || + channel.parameters() != null && !channel.parameters().isEmpty()) + { + options + .topic(KafkaTopicConfig::builder) + .name(topic) + .inject(topicConfig -> injectValue(topicConfig, channel.messages())) + .build() + .build(); + } + } + return options; + } + + private KafkaOptionsConfigBuilder injectKafkaBootstrapOptions( + KafkaOptionsConfigBuilder options) + { + return options.bootstrap(asyncApi.channels.values().stream() + .filter(c -> !PARAMETERIZED_TOPIC_PATTERN.matcher(c.address).find()) + .map(c -> AsyncapiChannelView.of(asyncApi.channels, c).address()).collect(Collectors.toList())); + } + + private KafkaTopicConfigBuilder injectValue( + KafkaTopicConfigBuilder topic, + Map messages) + { + if (messages != null) + { + if (hasJsonContentType()) + { + topic + .value(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(catalog -> injectSchemas(catalog, messages)) + .build() + .build(); + } + } + return topic; + } + + private CatalogedConfigBuilder injectSchemas( + CatalogedConfigBuilder catalog, + Map messages) + { + for (String name : messages.keySet()) + { + AsyncapiMessageView message = AsyncapiMessageView.of(asyncApi.components.messages, messages.get(name)); + AsyncapiSchemaView payload = AsyncapiSchemaView.of(asyncApi.components.schemas, message.payload()); + String subject = payload.refKey() != null ? payload.refKey() : name; + catalog + .schema() + .subject(subject) + .version(VERSION_LATEST) + .build() + .build(); + } + return catalog; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java new file mode 100644 index 0000000000..cf315bc69c --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AyncapiMqttProtocol.java @@ -0,0 +1,99 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiCompositeBindingAdapter.APPLICATION_JSON; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.mqtt.config.MqttConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.config.MqttOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public class AyncapiMqttProtocol extends AsyncapiProtocol +{ + private static final String SCHEME = "mqtt"; + private static final String SECURE_SCHEME = "mqtts"; + + public AyncapiMqttProtocol( + String qname, + Asyncapi asyncApi) + { + super(qname, asyncApi, SCHEME, SECURE_SCHEME); + } + + @Override + public BindingConfigBuilder injectProtocolServerOptions( + BindingConfigBuilder binding) + { + for (Map.Entry channelEntry : asyncApi.channels.entrySet()) + { + String topic = channelEntry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); + Map messages = channelEntry.getValue().messages; + if (hasJsonContentType()) + { + binding + .options(MqttOptionsConfig::builder) + .topic() + .name(topic) + .content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .inject(cataloged -> injectJsonSchemas(cataloged, messages, APPLICATION_JSON)) + .build() + .build() + .build() + .build() + .build(); + } + } + return binding; + } + + @Override + public BindingConfigBuilder injectProtocolServerRoutes( + BindingConfigBuilder binding) + { + for (Map.Entry entry : asyncApi.channels.entrySet()) + { + String topic = entry.getValue().address.replaceAll("\\{[^}]+\\}", "#"); + binding + .route() + .when(MqttConditionConfig::builder) + .publish() + .topic(topic) + .build() + .build() + .when(MqttConditionConfig::builder) + .subscribe() + .topic(topic) + .build() + .build() + .exit(qname) + .build(); + } + return binding; + } + + @Override + protected boolean isSecure() + { + return findFirstServerUrlWithScheme(SECURE_SCHEME) != null; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java new file mode 100644 index 0000000000..cf7c05c1d3 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java @@ -0,0 +1,254 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CACHE_CLIENT; +import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.agrona.AsciiSequenceView; +import org.agrona.DirectBuffer; +import org.agrona.collections.Int2ObjectHashMap; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class AsyncapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final AsyncapiOptionsConfig options; + public final List routes; + private final Int2ObjectHashMap composites; + private final long overrideRouteId; + private final Long2LongHashMap compositeResolvedIds; + private final HttpHeaderHelper helper; + private final Object2ObjectHashMap paths; + private final Map operationIds; + + public AsyncapiBindingConfig( + BindingConfig binding, + long overrideRouteId) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.overrideRouteId = overrideRouteId; + this.options = AsyncapiOptionsConfig.class.cast(binding.options); + this.routes = binding.routes.stream().map(r -> new AsyncapiRouteConfig(r, options::resolveApiId)).collect(toList()); + this.compositeResolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || + b.type.equals("kafka") && b.kind == CACHE_CLIENT || b.type.equals("mqtt-kafka")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(options.specs.get(0).apiId, r.id), + (m, r) -> m, + IDENTITY_FINISH + )); + this.composites = new Int2ObjectHashMap<>(); + binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("mqtt") || b.type.equals("http") || b.type.equals("kafka") && b.kind == CACHE_CLIENT || + b.type.equals("mqtt-kafka")) + .forEach(b -> this.composites.put(NamespacedId.namespaceId(b.id), b.type)); + + this.paths = new Object2ObjectHashMap<>(); + options.specs.forEach(c -> c.asyncapi.channels.forEach((k, v) -> + { + String regex = v.address.replaceAll("\\{[^/]+}", "[^/]+"); + regex = "^" + regex + "$"; + Pattern pattern = Pattern.compile(regex); + paths.put(pattern.matcher(""), k); + })); + + this.helper = new HttpHeaderHelper(); + + Map resolversByMethod = new TreeMap<>(CharSequence::compare); + options.specs.forEach(c -> c.asyncapi.operations.forEach((k, v) -> + { + String[] refParts = v.channel.ref.split("/"); + resolversByMethod.put(refParts[refParts.length - 1], k); + })); + this.operationIds = unmodifiableMap(resolversByMethod); + } + + public boolean isCompositeOriginId( + long originId) + { + return composites.containsKey(NamespacedId.namespaceId(originId)); + } + + public String getCompositeOriginType( + long originId) + { + return composites.get(NamespacedId.namespaceId(originId)); + } + + public long resolveCompositeResolvedId( + long apiId) + { + return overrideRouteId != -1 ? overrideRouteId : compositeResolvedIds.get(apiId); + } + + public String resolveOperationId( + HttpBeginExFW httpBeginEx) + { + helper.visit(httpBeginEx); + + String operationId = null; + + for (Map.Entry item : paths.entrySet()) + { + Matcher matcher = item.getKey(); + matcher.reset(helper.path); + if (matcher.find()) + { + String channelName = item.getValue(); + operationId = operationIds.get(channelName); + break; + } + } + + return operationId; + } + + public AsyncapiRouteConfig resolve( + long authorization) + { + return routes.stream() + .filter(r -> r.authorized(authorization)) + .findFirst() + .orElse(null); + } + + public AsyncapiRouteConfig resolve( + long authorization, + long apiId) + { + return routes.stream() + .filter(r -> r.authorized(authorization) && r.matches(apiId)) + .findFirst() + .orElse(null); + } + + private static final class HttpHeaderHelper + { + private static final String8FW HEADER_NAME_METHOD = new String8FW(":method"); + private static final String8FW HEADER_NAME_PATH = new String8FW(":path"); + private static final String8FW HEADER_NAME_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_NAME_AUTHORITY = new String8FW(":authority"); + + private final Map> visitors; + { + Map> visitors = new HashMap<>(); + visitors.put(HEADER_NAME_METHOD, this::visitMethod); + visitors.put(HEADER_NAME_PATH, this::visitPath); + visitors.put(HEADER_NAME_SCHEME, this::visitScheme); + visitors.put(HEADER_NAME_AUTHORITY, this::visitAuthority); + this.visitors = visitors; + } + private final AsciiSequenceView methodRO = new AsciiSequenceView(); + private final AsciiSequenceView pathRO = new AsciiSequenceView(); + private final String16FW schemeRO = new String16FW(); + private final String16FW authorityRO = new String16FW(); + + public CharSequence path; + public CharSequence method; + public String16FW scheme; + public String16FW authority; + + private void visit( + HttpBeginExFW beginEx) + { + method = null; + path = null; + scheme = null; + authority = null; + + if (beginEx != null) + { + beginEx.headers().forEach(this::dispatch); + } + } + + private boolean dispatch( + HttpHeaderFW header) + { + final String8FW name = header.name(); + final Consumer visitor = visitors.get(name); + if (visitor != null) + { + visitor.accept(header.value()); + } + + return method != null && + path != null && + scheme != null && + authority != null; + } + + private void visitMethod( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + method = methodRO.wrap(buffer, offset, length); + } + + private void visitPath( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + path = pathRO.wrap(buffer, offset, length); + } + + private void visitScheme( + String16FW value) + { + scheme = schemeRO.wrap(value.buffer(), value.offset(), value.limit()); + } + + private void visitAuthority( + String16FW value) + { + authority = authorityRO.wrap(value.buffer(), value.offset(), value.limit()); + } + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java new file mode 100644 index 0000000000..b3e3f70c62 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; + +public class AsyncapiConditionConfig extends ConditionConfig +{ + public final String apiId; + public final String operationId; + + public AsyncapiConditionConfig( + String apiId, + String operationId) + { + this.apiId = apiId; + this.operationId = operationId; + } + + public boolean matches( + long apiId, + Function supplyApiId) + { + return matchesApiId(apiId, supplyApiId); + } + + private boolean matchesApiId( + long apiId, + Function supplyApiId) + { + return supplyApiId.apply(this.apiId) == apiId; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java new file mode 100644 index 0000000000..8d659d7141 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapter.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi; + +public class AsyncapiConditionConfigAdapter implements ConditionConfigAdapterSpi, JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + ConditionConfig condition) + { + AsyncapiConditionConfig asyncapiCondition = (AsyncapiConditionConfig) condition; + JsonObjectBuilder object = Json.createObjectBuilder(); + + object.add(API_ID_NAME, asyncapiCondition.apiId); + object.add(OPERATION_ID_NAME, asyncapiCondition.operationId); + + return object.build(); + } + + @Override + public ConditionConfig adaptFromJson( + JsonObject object) + { + String apiId = object.getString(API_ID_NAME); + String operationId = object.getString(OPERATION_ID_NAME); + return new AsyncapiConditionConfig(apiId, operationId); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..e7c61ed268 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapter.java @@ -0,0 +1,266 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static java.util.stream.Collectors.toList; + +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.zip.CRC32C; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonString; +import jakarta.json.JsonValue; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfigBuilder; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfigBuilder; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiParser; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class AsyncapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String SPECS_NAME = "specs"; + private static final String TCP_NAME = "tcp"; + private static final String TLS_NAME = "tls"; + private static final String HTTP_NAME = "http"; + private static final String KAFKA_NAME = "kafka"; + private static final String MQTT_KAFKA_NAME = "mqtt_kafka"; + private static final String CHANNELS_NAME = "channels"; + private static final String SESSIONS_NAME = "sessions"; + private static final String MESSAGES_NAME = "messages"; + private static final String RETAINED_NAME = "retained"; + + private final AsyncapiParser parser; + private final CRC32C crc; + + private OptionsConfigAdapter tcpOptions; + private OptionsConfigAdapter tlsOptions; + private OptionsConfigAdapter httpOptions; + private OptionsConfigAdapter kafkaOptions; + private Function readURL; + + public AsyncapiOptionsConfigAdapter() + { + this.parser = new AsyncapiParser(); + this.crc = new CRC32C(); + } + + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + AsyncapiOptionsConfig asyncapiOptions = (AsyncapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (asyncapiOptions.specs != null) + { + JsonObjectBuilder specs = Json.createObjectBuilder(); + asyncapiOptions.specs.forEach(p -> specs.add(p.apiLabel, p.location)); + object.add(SPECS_NAME, specs); + } + + if (asyncapiOptions.tcp != null) + { + final TcpOptionsConfig tcp = asyncapiOptions.tcp; + object.add(TCP_NAME, tcpOptions.adaptToJson(tcp)); + } + + if (asyncapiOptions.tls != null) + { + final TlsOptionsConfig tls = asyncapiOptions.tls; + object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); + } + + if (asyncapiOptions.http != null) + { + final HttpOptionsConfig http = asyncapiOptions.http; + object.add(HTTP_NAME, httpOptions.adaptToJson(http)); + } + + if (asyncapiOptions.kafka != null) + { + final KafkaOptionsConfig kafka = asyncapiOptions.kafka; + object.add(KAFKA_NAME, kafkaOptions.adaptToJson(kafka)); + } + + if (asyncapiOptions.mqttKafka != null) + { + AsyncapiMqttKafkaConfig mqttKafka = asyncapiOptions.mqttKafka; + JsonObjectBuilder newMqttKafka = Json.createObjectBuilder(); + AsyncapiChannelsConfig channels = mqttKafka.channels; + if (channels != null) + { + JsonObjectBuilder newChannels = Json.createObjectBuilder(); + String sessions = channels.sessions; + if (sessions != null) + { + newChannels.add(SESSIONS_NAME, sessions); + } + + String messages = channels.messages; + if (messages != null) + { + newChannels.add(MESSAGES_NAME, messages); + } + + String retained = channels.retained; + if (retained != null) + { + newChannels.add(RETAINED_NAME, retained); + } + newMqttKafka.add(CHANNELS_NAME, newChannels); + + object.add(MQTT_KAFKA_NAME, newMqttKafka); + } + } + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + final AsyncapiOptionsConfigBuilder asyncapiOptions = AsyncapiOptionsConfig.builder(); + + List specs = object.containsKey(SPECS_NAME) + ? asListAsyncapis(object.getJsonObject(SPECS_NAME)) + : null; + asyncapiOptions.specs(specs); + + if (object.containsKey(TCP_NAME)) + { + final JsonObject tcp = object.getJsonObject(TCP_NAME); + final TcpOptionsConfig tcpOptions = (TcpOptionsConfig) this.tcpOptions.adaptFromJson(tcp); + asyncapiOptions.tcp(tcpOptions); + } + + if (object.containsKey(TLS_NAME)) + { + final JsonObject tls = object.getJsonObject(TLS_NAME); + final TlsOptionsConfig tlsOptions = (TlsOptionsConfig) this.tlsOptions.adaptFromJson(tls); + asyncapiOptions.tls(tlsOptions); + } + + if (object.containsKey(HTTP_NAME)) + { + final JsonObject http = object.getJsonObject(HTTP_NAME); + final HttpOptionsConfig httpOptions = (HttpOptionsConfig) this.httpOptions.adaptFromJson(http); + asyncapiOptions.http(httpOptions); + } + + if (object.containsKey(KAFKA_NAME)) + { + final JsonObject kafka = object.getJsonObject(KAFKA_NAME); + final KafkaOptionsConfig kafkaOptions = (KafkaOptionsConfig) this.kafkaOptions.adaptFromJson(kafka); + asyncapiOptions.kafka(kafkaOptions); + } + + if (object.containsKey(MQTT_KAFKA_NAME)) + { + AsyncapiMqttKafkaConfigBuilder mqttKafkaBuilder = AsyncapiMqttKafkaConfig.builder(); + final JsonObject mqttKafka = object.getJsonObject(MQTT_KAFKA_NAME); + if (mqttKafka.containsKey(CHANNELS_NAME)) + { + AsyncapiChannelsConfigBuilder channelsBuilder = AsyncapiChannelsConfig.builder(); + JsonObject channels = mqttKafka.getJsonObject(CHANNELS_NAME); + + if (channels.containsKey(SESSIONS_NAME)) + { + channelsBuilder.sessions(channels.getString(SESSIONS_NAME)); + } + if (channels.containsKey(MESSAGES_NAME)) + { + channelsBuilder.messages(channels.getString(MESSAGES_NAME)); + } + if (channels.containsKey(RETAINED_NAME)) + { + channelsBuilder.retained(channels.getString(RETAINED_NAME)); + } + asyncapiOptions.mqttKafka(mqttKafkaBuilder.channels(channelsBuilder.build()).build()); + } + } + return asyncapiOptions.build(); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + this.tcpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tcpOptions.adaptType("tcp"); + this.tlsOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tlsOptions.adaptType("tls"); + this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.httpOptions.adaptType("http"); + this.kafkaOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.kafkaOptions.adaptType("kafka"); + } + + private List asListAsyncapis( + JsonObject array) + { + return array.entrySet().stream() + .map(this::asAsyncapi) + .collect(toList()); + } + + private AsyncapiConfig asAsyncapi( + Map.Entry entry) + { + final String apiLabel = entry.getKey(); + final String location = ((JsonString) entry.getValue()).getString(); + final String specText = readURL.apply(location); + crc.reset(); + crc.update(specText.getBytes(StandardCharsets.UTF_8)); + final long apiId = crc.getValue(); + Asyncapi asyncapi = parser.parse(specText); + + return new AsyncapiConfig(apiLabel, apiId, location, asyncapi); + } + +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java new file mode 100644 index 0000000000..00545968fe --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiRouteConfig.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.function.Function; +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class AsyncapiRouteConfig +{ + public final long id; + public final AsyncapiWithConfig with; + public final List when; + private final LongPredicate authorized; + private final Function supplyApiId; + + public AsyncapiRouteConfig( + RouteConfig route, + Function supplyApiId) + { + this.id = route.id; + this.authorized = route.authorized; + this.when = route.when.stream() + .map(AsyncapiConditionConfig.class::cast) + .collect(toList()); + this.with = (AsyncapiWithConfig) route.with; + this.supplyApiId = supplyApiId; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } + + boolean matches( + long apiId) + { + return when.isEmpty() || when.stream().anyMatch(m -> m.matches(apiId, supplyApiId)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java similarity index 66% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java rename to incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java index 5d7b0b77d7..285af68e8e 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfig.java @@ -12,17 +12,20 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; import io.aklivity.zilla.runtime.engine.config.WithConfig; -public class MqttKafkaWithConfig extends WithConfig +public class AsyncapiWithConfig extends WithConfig { - public final String messages; + public final String apiId; + public final String operationId; - public MqttKafkaWithConfig( - String messages) + public AsyncapiWithConfig( + String apiId, + String operationId) { - this.messages = messages; + this.apiId = apiId; + this.operationId = operationId; } } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java new file mode 100644 index 0000000000..7fa0347114 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.WithConfig; +import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; + +public class AsyncapiWithConfigAdapter implements WithConfigAdapterSpi, JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return AsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + WithConfig with) + { + AsyncapiWithConfig config = (AsyncapiWithConfig) with; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (config.apiId != null) + { + object.add(API_ID_NAME, config.apiId); + } + + if (config.operationId != null) + { + object.add(OPERATION_ID_NAME, config.operationId); + } + + return object.build(); + } + + @Override + public WithConfig adaptFromJson( + JsonObject object) + { + String apiId = object.containsKey(API_ID_NAME) + ? object.getString(API_ID_NAME) + : null; + + String operationId = object.containsKey(OPERATION_ID_NAME) + ? object.getString(OPERATION_ID_NAME) + : null; + + return new AsyncapiWithConfig(apiId, operationId); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java similarity index 63% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java rename to incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java index cb5ee737f1..0894282dac 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceAsyncHeaderConfig.java +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/Asyncapi.java @@ -12,18 +12,14 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; -public final class HttpKafkaWithProduceAsyncHeaderConfig -{ - public final String name; - public final String value; +import java.util.Map; - public HttpKafkaWithProduceAsyncHeaderConfig( - String name, - String value) - { - this.name = name; - this.value = value; - } +public class Asyncapi +{ + public Map servers; + public Map channels; + public Map operations; + public AsyncapiComponents components; } diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java new file mode 100644 index 0000000000..29eb4fe648 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiBinding.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiBinding +{ + public String method; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java new file mode 100644 index 0000000000..ddfc9021f1 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiChannel.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.LinkedHashMap; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class AsyncapiChannel +{ + public String address; + public LinkedHashMap messages; + public LinkedHashMap parameters; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java new file mode 100644 index 0000000000..aeaa0e9c6b --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiComponents.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.Map; + +public class AsyncapiComponents +{ + public Map securitySchemes; + public Map messages; + public Map schemas; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java new file mode 100644 index 0000000000..dbdff777ee --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiItem.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiItem +{ + public String type; + public String description; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java new file mode 100644 index 0000000000..70fd6ab122 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiMessage.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class AsyncapiMessage +{ + public AsyncapiSchema headers; + public String contentType; + public AsyncapiSchema payload; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java new file mode 100644 index 0000000000..1c53959db8 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiOperation.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.Map; + +public class AsyncapiOperation +{ + public Map bindings; + public AsyncapiChannel channel; + public String action; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java new file mode 100644 index 0000000000..af864bab5b --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiParameter.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiParameter +{ + public AsyncapiSchema schema; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java new file mode 100644 index 0000000000..6440924e6d --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSchema.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class AsyncapiSchema +{ + public String type; + public AsyncapiSchema items; + public Map properties; + public List required; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java new file mode 100644 index 0000000000..96ba909021 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiSecurityScheme.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +public class AsyncapiSecurityScheme +{ + public String bearerFormat; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java new file mode 100644 index 0000000000..bcf34983e3 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/model/AsyncapiServer.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + +import jakarta.json.JsonArray; + +public class AsyncapiServer +{ + public String host; + public String protocol; + public JsonArray security; +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java new file mode 100644 index 0000000000..3a4e590864 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiClientFactory.java @@ -0,0 +1,1067 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class AsyncapiClientFactory implements AsyncapiStreamFactory +{ + private static final String MQTT_TYPE_NAME = "mqtt"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int asyncapiTypeId; + private final int mqttTypeId; + private final AsyncapiConfiguration config; + + + public AsyncapiClientFactory( + AsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + this.mqttTypeId = context.supplyTypeId(MQTT_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return mqttTypeId; + } + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + AsyncapiBindingConfig asyncapiBinding = new AsyncapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, asyncapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + + final AsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + final long apiId = asyncapiBeginEx.apiId(); + final String operationId = asyncapiBeginEx.operationId().asString(); + + final long resolvedId = binding.resolveCompositeResolvedId(apiId); + + if (resolvedId != -1) + { + newStream = new AsyncapiStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + resolvedId, + operationId)::onAsyncapiMessage; + } + + } + + return newStream; + } + + private final class AsyncapiStream + { + private final CompositeStream composite; + private final MessageConsumer sender; + private final String operationId; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private AsyncapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.composite = new CompositeStream(this, routedId, resolvedId, authorization); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onAsyncapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiReset(reset); + break; + default: + break; + } + } + + private void onAsyncapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = AsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + composite.doCompositeBegin(traceId, asyncapiBeginEx.extension()); + } + + private void onAsyncapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + composite.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + composite.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + composite.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + composite.doCompositeAbort(traceId, extension); + } + + private void onAsyncapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = AsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onAsyncapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + composite.doCompositeWindow(traceId, acknowledge, budgetId, padding); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, asyncapiBeginEx); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = composite.initialAck; + initialMax = composite.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doCompositeData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = AsyncapiState.closeReply(state); + } + + private void doCompositeAbort( + long traceId) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = AsyncapiState.closeInitial(state); + } + + private void doAsyncapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + } + + private void cleanup( + long traceId) + { + doCompositeReset(traceId); + doCompositeAbort(traceId); + + composite.cleanup(traceId); + } + } + + final class CompositeStream + { + private final AsyncapiStream delegate; + private final long originId; + private final long routedId; + private final long authorization; + + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeStream( + AsyncapiStream delegate, + long originId, + long routedId, + long authorization) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onCompositeMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, extension); + state = AsyncapiState.openingInitial(state); + } + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void onCompositeMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + delegate.doCompositeBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, flags, reserved, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doAsyncapiFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java new file mode 100644 index 0000000000..bd90cbe7a3 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiProxyFactory.java @@ -0,0 +1,1764 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import java.util.Optional; +import java.util.function.Function; +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class AsyncapiProxyFactory implements AsyncapiStreamFactory +{ + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Function supplyTypeId; + private final Long2ObjectHashMap bindings; + private final Long2LongHashMap apiIds; + private final int asyncapiTypeId; + + private final AsyncapiConfiguration config; + + public AsyncapiProxyFactory( + AsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTypeId = context::supplyTypeId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.apiIds = new Long2LongHashMap(-1); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + } + + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + AsyncapiBindingConfig asyncapiBinding = new AsyncapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, asyncapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + + final AsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + if (!binding.isCompositeOriginId(originId)) + { + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final long apiId = asyncapiBeginEx.apiId(); + final String operationId = asyncapiBeginEx.operationId().asString(); + + final long compositeResolvedId = binding.resolveCompositeResolvedId(apiId); + apiIds.put(apiId, apiId); + + if (compositeResolvedId != -1) + { + newStream = new AsyncapiServerStream( + receiver, + originId, + routedId, + initialId, + apiId, + authorization, + compositeResolvedId, + operationId)::onAsyncapiServerMessage; + } + } + else + { + final long apiId = apiIds.get(affinity); + final AsyncapiRouteConfig route = binding.resolve(authorization, apiId); + + if (route != null) + { + final long clientApiId = binding.options.resolveApiId(route.with.apiId); + newStream = new CompositeClientStream( + receiver, + originId, + routedId, + initialId, + authorization, + route.id, + affinity, + clientApiId)::onCompositeClientMessage; + } + else + { + // Needed by the willStream which comes directly from the composite without Asyncapi begin pair + Optional asyncapiRoute = binding.routes.stream().findFirst(); + final long routeId = asyncapiRoute.map(r -> r.id).orElse(0L); + final long clientApiId = asyncapiRoute.map(asyncapiRouteConfig -> + binding.options.resolveApiId(asyncapiRouteConfig.with.apiId)).orElse(0L); + + newStream = new CompositeClientStream( + receiver, + originId, + routedId, + initialId, + authorization, + routeId, + affinity, + clientApiId)::onCompositeClientMessage; + } + } + } + + return newStream; + } + + private final class AsyncapiServerStream + { + private final CompositeServerStream delegate; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private AsyncapiServerStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long compositeResolvedId, + String operationId) + { + this.delegate = + new CompositeServerStream(this, compositeResolvedId, compositeResolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onAsyncapiServerMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiServerBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiServerData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiServerEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiServerFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiServerAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiServerWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiServerReset(reset); + break; + default: + break; + } + } + + private void onAsyncapiServerBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = AsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + delegate.doCompositeBegin(traceId, affinity, asyncapiBeginEx.extension()); + } + + private void onAsyncapiServerData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiServerEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiServerFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiServerAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeAbort(traceId, extension); + } + + private void doAsyncapiServerReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doAsyncapiServerWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = delegate.initialAck; + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doAsyncapiServerBegin( + long traceId, + OctetsFW extension) + { + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, asyncapiBeginEx); + } + + private void doAsyncapiServerData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doAsyncapiServerFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, reserved, extension); + } + + private void doAsyncapiServerEnd( + long traceId, + OctetsFW extension) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = AsyncapiState.closeReply(state); + } + + private void doAsyncapiServerAbort( + long traceId) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = AsyncapiState.closeInitial(state); + } + + private void onAsyncapiServerReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = AsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onAsyncapiServerWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeWindow(traceId, acknowledge, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doAsyncapiServerReset(traceId); + doAsyncapiServerAbort(traceId); + + delegate.cleanup(traceId); + } + } + + final class CompositeServerStream + { + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private AsyncapiServerStream delegate; + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeServerStream( + AsyncapiServerStream delegate, + long routedId, + long compositeResolvedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = routedId; + this.routedId = compositeResolvedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onCompositeServerMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + delegate.doAsyncapiServerBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doAsyncapiServerData(traceId, flags, reserved, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doAsyncapiServerFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiServerEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiServerAbort(traceId); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doAsyncapiServerReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiServerWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + long affinity, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onCompositeServerMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, affinity, extension); + state = AsyncapiState.openingInitial(state); + } + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class CompositeClientStream + { + private final long originId; + private final long routedId; + private final MessageConsumer sender; + private final long affinity; + private final long authorization; + private final AsyncapiClientStream delegate; + + private long initialId; + private long replyId; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeClientStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long authorization, + long resolvedId, + long affinity, + long apiId) + { + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.delegate = new AsyncapiClientStream(this, originId, resolvedId, authorization, apiId); + } + + private void onCompositeClientMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + + state = AsyncapiState.openingInitial(state); + + delegate.doAsyncapiClientBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiClientAbort(traceId, EMPTY_OCTETS); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + + replyAck = acknowledge; + + state = AsyncapiState.closeReply(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doAsyncapiClientReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = AsyncapiState.openReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiClientWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + initialAck = Math.max(delegate.initialAck, 0); + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + replySeq = delegate.replySeq; + replyAck = delegate.replyAck; + replyMax = delegate.replyMax; + state = AsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + replySeq += reserved; + + assert replySeq <= replyAck + replyMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = AsyncapiState.closeReply(state); + + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, traceId, authorization, extension); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = AsyncapiState.closeReply(state); + + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class AsyncapiClientStream + { + private final long originId; + private final long routedId; + private final long authorization; + private final CompositeClientStream delegate; + private final long apiId; + + private long initialId; + private long replyId; + private MessageConsumer asyncapi; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private AsyncapiClientStream( + CompositeClientStream delegate, + long originId, + long routedId, + long authorization, + long apiId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + this.apiId = apiId; + } + + private void onAsyncapiClientMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiClientBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiClientData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiClientFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiClientEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiClientAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiClientReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiClientWindow(window); + break; + default: + break; + } + } + + private void onAsyncapiClientBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final OctetsFW asyncapiExtension = asyncapiBeginEx != null ? asyncapiBeginEx.extension() : EMPTY_OCTETS; + + delegate.doCompositeBegin(traceId, asyncapiExtension); + } + + private void onAsyncapiClientData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiClientFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiClientEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiClientAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId, EMPTY_OCTETS); + } + + private void onAsyncapiClientReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onAsyncapiClientWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void doAsyncapiClientReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doAsyncapiClientWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doAsyncapiClientBegin( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .apiId(apiId) + .operationId((String) null) + .extension(extension) + .build(); + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.asyncapi = newStream(this::onAsyncapiClientMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, asyncapiBeginEx); + state = AsyncapiState.openingInitial(state); + } + } + + private void doAsyncapiClientData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doAsyncapiClientFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doAsyncapiClientEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doAsyncapiClientAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doAsyncapiClientAbort(traceId, EMPTY_OCTETS); + delegate.doCompositeReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java new file mode 100644 index 0000000000..708507f8f4 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiServerFactory.java @@ -0,0 +1,1085 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import java.util.function.Function; +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.MqttBeginExFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class AsyncapiServerFactory implements AsyncapiStreamFactory +{ + private static final String MQTT_TYPE_NAME = "mqtt"; + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + private final BeginFW beginRO = new BeginFW(); + private final BeginFW compositeBeginRO = new BeginFW(); + private final HttpBeginExFW httpBeginRO = new HttpBeginExFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + private final MqttBeginExFW mqttBeginExRO = new MqttBeginExFW(); + private final HttpBeginExFW httpBeginExRO = new HttpBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Function supplyTypeId; + private final Long2ObjectHashMap bindings; + private final int asyncapiTypeId; + private final int mqttTypeId; + private final int httpTypeId; + private final AsyncapiConfiguration config; + + public AsyncapiServerFactory( + AsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTypeId = context::supplyTypeId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + this.mqttTypeId = context.supplyTypeId(MQTT_TYPE_NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return mqttTypeId; + } + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + AsyncapiBindingConfig asyncapiBinding = new AsyncapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, asyncapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + + final AsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null && binding.isCompositeOriginId(originId)) + { + final AsyncapiRouteConfig route = binding.resolve(authorization); + + if (route != null) + { + final int compositeTypeId = supplyTypeId.apply(binding.getCompositeOriginType(originId)); + + final String operationId = compositeTypeId == httpTypeId ? + binding.resolveOperationId(extension.get(httpBeginRO::tryWrap)) : null; + final long apiId = binding.options.specs.get(0).apiId; + newStream = new CompositeStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + route.id, + apiId, + operationId)::onCompositeMessage; + } + } + + return newStream; + } + + private final class CompositeStream + { + private final AsyncapiStream delegate; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private CompositeStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + long apiId, + String operationId) + { + this.delegate = new AsyncapiStream(this, routedId, resolvedId, authorization, apiId, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onCompositeMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = AsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiEnd(traceId, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiFlush(traceId, reserved, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = AsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiAbort(traceId, extension); + } + + private void doCompositeReset( + long traceId) + { + if (!AsyncapiState.initialClosed(state)) + { + state = AsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = delegate.initialAck; + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + state = AsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doCompositeData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = AsyncapiState.closeReply(state); + } + + private void doCompositeAbort( + long traceId) + { + if (AsyncapiState.replyOpening(state) && !AsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = AsyncapiState.closeInitial(state); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = AsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiWindow(traceId, acknowledge, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doCompositeReset(traceId); + doCompositeAbort(traceId); + + delegate.cleanup(traceId); + } + } + + final class AsyncapiStream + { + private final CompositeStream delegate; + private final String operationId; + private final long apiId; + private final long originId; + private final long routedId; + private final long authorization; + + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private AsyncapiStream( + CompositeStream delegate, + long originId, + long routedId, + long authorization, + long apiId, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.apiId = apiId; + this.operationId = operationId; + } + + private void onAsyncapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiWindow(window); + break; + default: + break; + } + } + + private void onAsyncapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = AsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final OctetsFW asyncapiExtension = asyncapiBeginEx != null ? asyncapiBeginEx.extension() : EMPTY_OCTETS; + + delegate.doCompositeBegin(traceId, asyncapiExtension); + } + + private void onAsyncapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, flags, reserved, payload, extension); + } + + private void onAsyncapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = AsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId); + } + + private void onAsyncapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = AsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onAsyncapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = AsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void doAsyncapiReset( + long traceId) + { + if (!AsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = AsyncapiState.closeReply(state); + } + } + + private void doAsyncapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doAsyncapiBegin( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialOpening(state)) + { + assert state == 0; + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .apiId(apiId) + .operationId(operationId) + .extension(extension) + .build(); + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onAsyncapiMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, asyncapiBeginEx); + state = AsyncapiState.openingInitial(state); + } + } + + private void doAsyncapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doAsyncapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doAsyncapiEnd( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void doAsyncapiAbort( + long traceId, + OctetsFW extension) + { + if (!AsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = AsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doAsyncapiAbort(traceId, EMPTY_OCTETS); + doAsyncapiReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java new file mode 100644 index 0000000000..8a8724fd20 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +public final class AsyncapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENED = 0x01; + private static final int REPLY_OPENING = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return openingReply(state) | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + private AsyncapiState() + { + // utility + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java new file mode 100644 index 0000000000..7a8c299e21 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/AsyncapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface AsyncapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java new file mode 100644 index 0000000000..7b087a05ad --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiChannelView.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiChannel; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiParameter; + +public final class AsyncapiChannelView extends AsyncapiResolvable +{ + private final AsyncapiChannel channel; + + public String address() + { + return channel.address; + } + + public Map messages() + { + return channel.messages; + } + + public Map parameters() + { + return channel.parameters; + } + + public static AsyncapiChannelView of( + Map channels, + AsyncapiChannel asyncapiChannel) + { + return new AsyncapiChannelView(channels, asyncapiChannel); + } + + private AsyncapiChannelView( + Map channels, + AsyncapiChannel channel) + { + super(channels, "#/channels/(\\w+)"); + this.channel = channel.ref == null ? channel : resolveRef(channel.ref); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java new file mode 100644 index 0000000000..a1087313ad --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiMessageView.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiMessage; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema; + +public final class AsyncapiMessageView extends AsyncapiResolvable +{ + private final AsyncapiMessage message; + + public String refKey() + { + return key; + } + + public AsyncapiSchema headers() + { + return message.headers; + } + + public String contentType() + { + return message.contentType; + } + + public AsyncapiSchema payload() + { + return message.payload; + } + + public static AsyncapiMessageView of( + Map messages, + AsyncapiMessage asyncapiMessage) + { + return new AsyncapiMessageView(messages, asyncapiMessage); + } + + private AsyncapiMessageView( + Map messages, + AsyncapiMessage message) + { + super(messages, "#/components/messages/(\\w+)"); + this.message = message.ref == null ? message : resolveRef(message.ref); + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java new file mode 100644 index 0000000000..2334037c55 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiResolvable.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class AsyncapiResolvable +{ + private final Map map; + private final Matcher matcher; + + protected String key; + + public AsyncapiResolvable( + Map map, + String regex) + { + this.map = map; + this.matcher = Pattern.compile(regex).matcher(""); + } + + protected T resolveRef( + String ref) + { + T result = null; + if (matcher.reset(ref).matches()) + { + key = matcher.group(1); + result = map.get(key); + } + return result; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java new file mode 100644 index 0000000000..8755f88bb2 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiSchemaView.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbPropertyOrder; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiItem; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiSchema; + +@JsonbPropertyOrder({ + "type", + "items", + "properties", + "required" +}) +public final class AsyncapiSchemaView extends AsyncapiResolvable +{ + private static final String ARRAY_TYPE = "array"; + + private final AsyncapiSchema schema; + private final Map schemas; + + public String getType() + { + return schema.type; + } + + public String refKey() + { + return key; + } + + public AsyncapiSchemaView getItems() + { + return schema.items == null ? null : AsyncapiSchemaView.of(schemas, schema.items); + } + + public Map getProperties() + { + return schema.properties; + } + + public List getRequired() + { + return schema.required; + } + + public static AsyncapiSchemaView of( + Map schemas, + AsyncapiSchema asyncapiSchema) + { + return new AsyncapiSchemaView(schemas, asyncapiSchema); + } + + private AsyncapiSchemaView( + Map schemas, + AsyncapiSchema schema) + { + super(schemas, "#/components/schemas/(\\w+)"); + if (schema.ref != null) + { + schema = resolveRef(schema.ref); + } + else if (ARRAY_TYPE.equals(schema.type) && schema.items != null && schema.items.ref != null) + { + schema.items = resolveRef(schema.items.ref); + } + this.schemas = schemas; + this.schema = schema; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java new file mode 100644 index 0000000000..e1df7b7158 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiServerView.java @@ -0,0 +1,85 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.net.URI; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiServer; + +public final class AsyncapiServerView +{ + private final AsyncapiServer server; + private final ObjectMapper objectMapper = new ObjectMapper(); + + public URI url() + { + return URI.create(server.host); + } + + public List>> security() + { + List>> security = null; + if (server.security != null) + { + try + { + security = objectMapper.readValue(server.security.toString(), new TypeReference<>() + { + }); + } + catch (JsonProcessingException e) + { + rethrowUnchecked(e); + } + } + + return security; + } + + public String protocol() + { + return server.protocol; + } + + public String scheme() + { + return url().getScheme(); + } + + public String authority() + { + return String.format("%s:%d", url().getHost(), url().getPort()); + } + + public static AsyncapiServerView of( + AsyncapiServer asyncapiServer) + { + return new AsyncapiServerView(asyncapiServer); + } + + private AsyncapiServerView( + AsyncapiServer server) + { + this.server = server; + } +} diff --git a/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java new file mode 100644 index 0000000000..c05cb95ed1 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/view/AsyncapiView.java @@ -0,0 +1,81 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + +import static java.util.Objects.requireNonNull; + +import java.net.URI; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; + +public final class AsyncapiView +{ + private final Asyncapi asyncapi; + + public int[] resolveAllPorts() + { + int[] ports = new int[asyncapi.servers.size()]; + String[] keys = asyncapi.servers.keySet().toArray(String[]::new); + for (int i = 0; i < asyncapi.servers.size(); i++) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncapi.servers.get(keys[i])); + URI url = server.url(); + ports[i] = url.getPort(); + } + return ports; + } + + public int[] resolvePortsForScheme( + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + public URI findFirstServerUrlWithScheme( + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (String key : asyncapi.servers.keySet()) + { + AsyncapiServerView server = AsyncapiServerView.of(asyncapi.servers.get(key)); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + public static AsyncapiView of( + Asyncapi asyncapi) + { + return new AsyncapiView(asyncapi); + } + + private AsyncapiView( + Asyncapi asyncapi) + { + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-asyncapi/src/main/moditect/module-info.java b/incubator/binding-asyncapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..54b0d936b6 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/moditect/module-info.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.asyncapi +{ + requires com.fasterxml.jackson.databind; + requires com.fasterxml.jackson.dataformat.yaml; + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.mqtt; + requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.kafka; + requires io.aklivity.zilla.runtime.binding.mqtt.kafka; + requires io.aklivity.zilla.runtime.binding.tcp; + requires io.aklivity.zilla.runtime.binding.tls; + requires io.aklivity.zilla.runtime.catalog.inline; + requires io.aklivity.zilla.runtime.guard.jwt; + requires io.aklivity.zilla.runtime.vault.filesystem; + requires io.aklivity.zilla.runtime.model.core; + requires io.aklivity.zilla.runtime.model.json; + requires org.leadpony.justify; + + opens io.aklivity.zilla.runtime.binding.asyncapi.internal.model; + opens io.aklivity.zilla.runtime.binding.asyncapi.internal.view; + + exports io.aklivity.zilla.runtime.binding.asyncapi.config; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiConditionConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiWithConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingAdapter; + +} diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..e932368241 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingFactorySpi diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..bdc77aaba9 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBindingAdapter diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi new file mode 100644 index 0000000000..34cdfb042f --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiConditionConfigAdapter diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..3a362743bc --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapter diff --git a/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi new file mode 100644 index 0000000000..5eb2c445a3 --- /dev/null +++ b/incubator/binding-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiWithConfigAdapter diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java new file mode 100644 index 0000000000..29ff6df714 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiBingingFactorySpiTest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiOptionsConfigAdapterTest; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public class AsyncapiBingingFactorySpiTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + String content; + try (InputStream resource = AsyncapiOptionsConfigAdapterTest.class + .getResourceAsStream("../../../../../specs/binding/asyncapi/config/mqtt/asyncapi.yaml")) + { + content = new String(resource.readAllBytes(), UTF_8); + } + Mockito.doReturn(content).when(context).readURL("mqtt/asyncapi.yaml"); + + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("asyncapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldAddCompositeBinding() + { + String text = + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + BindingConfig config = BindingConfig.builder() + .namespace("example") + .name("asyncapi0") + .type("asyncapi") + .kind(KindConfig.SERVER) + .options(options) + .build(); + + AsyncapiServerCompositeBindingAdapter adapter = new AsyncapiServerCompositeBindingAdapter(); + Assert.assertEquals(0, config.composites.size()); + BindingConfig newConfig = adapter.adapt(config); + Assert.assertEquals(1, newConfig.composites.size()); + Assert.assertEquals(2, newConfig.composites.get(0).bindings.size()); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java new file mode 100644 index 0000000000..0d04c63d37 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/AsyncapiConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal; + +import static io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfiguration.ASYNCAPI_TARGET_ROUTE_ID; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class AsyncapiConfigurationTest +{ + public static final String ASYNCAPI_TARGET_ROUTE_ID_NAME = "zilla.binding.asyncapi.target.route.id"; + + @Test + public void shouldVerifyConstants() throws Exception + { + assertEquals(ASYNCAPI_TARGET_ROUTE_ID.name(), ASYNCAPI_TARGET_ROUTE_ID_NAME); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java new file mode 100644 index 0000000000..c3257a3871 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiConditionConfigAdapterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class AsyncapiConditionConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new AsyncapiConditionConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadCondition() + { + String text = + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"testOperationId\"" + + "}"; + + AsyncapiConditionConfig condition = jsonb.fromJson(text, AsyncapiConditionConfig.class); + + assertThat(condition, not(nullValue())); + assertThat(condition.apiId, equalTo("test")); + assertThat(condition.operationId, equalTo("testOperationId")); + } + + @Test + public void shouldWriteCondition() + { + AsyncapiConditionConfig condition = new AsyncapiConditionConfig("test", "testOperationId"); + + String text = jsonb.toJson(condition); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"testOperationId\"" + + "}")); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..ef12eda389 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiOptionsConfigAdapterTest.java @@ -0,0 +1,562 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiChannelsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiMqttKafkaConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.asyncapi.AsyncapiSpecs; + +public class AsyncapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + private Jsonb jsonb; + + public void initJson( + String asyncapiConfig) throws IOException + { + try (InputStream resource = AsyncapiSpecs.class + .getResourceAsStream("config/" + asyncapiConfig)) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL(asyncapiConfig); + + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("asyncapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptionsMqtt() throws IOException + { + initJson("mqtt/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"," + + "}," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7183" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"mqtt.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"mqtt\"" + + "]" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("mqtt/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.tcp.host, equalTo("localhost")); + assertThat(options.tcp.ports, equalTo(new int[] { 7183 })); + assertThat(options.tls.keys, equalTo(asList("localhost"))); + assertThat(options.tls.trust, equalTo(asList("serverca"))); + assertThat(options.tls.trustcacerts, equalTo(true)); + assertThat(options.tls.sni, equalTo(asList("mqtt.example.net"))); + assertThat(options.tls.alpn, equalTo(asList("mqtt"))); + } + + @Test + public void shouldWriteOptionsMqtt() throws IOException + { + initJson("mqtt/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("mqtt-api", 1, "mqtt/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .tcp(TcpOptionsConfig.builder() + .host("localhost") + .ports(new int[] { 7183 }) + .build()) + .tls(TlsOptionsConfig.builder() + .keys(asList("localhost")) + .trust(asList("serverca")) + .sni(asList("mqtt.example.net")) + .alpn(asList("mqtt")) + .trustcacerts(true) + .build()) + .kafka(KafkaOptionsConfig.builder() + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7183" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"mqtt.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"mqtt\"" + + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"mqttSessions\"," + + "\"messages\":\"mqttMessages\"," + + "\"retained\":\"mqttRetained\"" + + "}" + + "}" + + "}")); + } + + @Test + public void shouldReadOptionsKafka() throws IOException + { + initJson("kafka/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "{" + + "\"kafka-api\":\"kafka/asyncapi.yaml\"," + + "}," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":9092" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"kafka.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"kafka\"" + + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("kafka/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.tcp.host, equalTo("localhost")); + assertThat(options.tcp.ports, equalTo(new int[] { 9092 })); + assertThat(options.tls.keys, equalTo(asList("localhost"))); + assertThat(options.tls.trust, equalTo(asList("serverca"))); + assertThat(options.tls.trustcacerts, equalTo(true)); + assertThat(options.tls.sni, equalTo(asList("kafka.example.net"))); + assertThat(options.tls.alpn, equalTo(asList("kafka"))); + assertThat(options.kafka.sasl.mechanism, equalTo("plain")); + assertThat(options.kafka.sasl.username, equalTo("username")); + assertThat(options.kafka.sasl.password, equalTo("password")); + } + + @Test + public void shouldWriteOptionsHttp() throws IOException + { + initJson("http/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("http-api", 1, "http/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .tcp(TcpOptionsConfig.builder() + .host("localhost") + .ports(new int[] { 7080 }) + .build()) + .tls(TlsOptionsConfig.builder() + .keys(asList("localhost")) + .trust(asList("serverca")) + .sni(asList("http.example.net")) + .alpn(asList("http")) + .trustcacerts(true) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "{" + + "\"http-api\":\"http/asyncapi.yaml\"" + + "}," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7080" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"http.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"http\"" + + "]" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"mqttSessions\"," + + "\"messages\":\"mqttMessages\"," + + "\"retained\":\"mqttRetained\"" + + "}" + + "}" + + "}")); + } + + @Test + public void shouldReadOptionsHttp() throws IOException + { + initJson("http/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "{" + + "\"http-api\":\"http/asyncapi.yaml\"," + + "}," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":7080" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"http.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"http\"" + + "]" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("http/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.tcp.host, equalTo("localhost")); + assertThat(options.tcp.ports, equalTo(new int[] { 7080 })); + assertThat(options.tls.keys, equalTo(asList("localhost"))); + assertThat(options.tls.trust, equalTo(asList("serverca"))); + assertThat(options.tls.trustcacerts, equalTo(true)); + assertThat(options.tls.sni, equalTo(asList("http.example.net"))); + assertThat(options.tls.alpn, equalTo(asList("http"))); + } + + @Test + public void shouldWriteOptionsKafka() throws IOException + { + initJson("kafka/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("kafka-api", 1, "kafka/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .tcp(TcpOptionsConfig.builder() + .host("localhost") + .ports(new int[] { 9092 }) + .build()) + .tls(TlsOptionsConfig.builder() + .keys(asList("localhost")) + .trust(asList("serverca")) + .sni(asList("kafka.example.net")) + .alpn(asList("kafka")) + .trustcacerts(true) + .build()) + .kafka(KafkaOptionsConfig.builder() + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "{" + + "\"kafka-api\":\"kafka/asyncapi.yaml\"" + + "}," + + "\"tcp\":" + + "{" + + "\"host\":\"localhost\"," + + "\"port\":9092" + + "}," + + "\"tls\":" + + "{" + + "\"keys\":" + + "[" + + "\"localhost\"" + + "]," + + "\"trust\":" + + "[" + + "\"serverca\"" + + "]," + + "\"trustcacerts\":true," + + "\"sni\":" + + "[" + + "\"kafka.example.net\"" + + "]," + + "\"alpn\":" + + "[" + + "\"kafka\"" + + "]" + + "}," + + "\"kafka\":" + + "{" + + "\"sasl\":" + + "{" + + "\"mechanism\":\"plain\"," + + "\"username\":\"username\"," + + "\"password\":\"password\"" + + "}" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"mqttSessions\"," + + "\"messages\":\"mqttMessages\"," + + "\"retained\":\"mqttRetained\"" + + "}" + + "}" + + "}")); + } + + @Test + public void shouldReadOptionsMqttKafka() throws IOException + { + initJson("mqtt/asyncapi.yaml"); + String text = + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"sessionsChannel\"," + + "\"messages\":\"messagesChannel\"," + + "\"retained\":\"retainedChannel\"" + + "}" + + "}" + + "}"; + + AsyncapiOptionsConfig options = jsonb.fromJson(text, AsyncapiOptionsConfig.class); + + assertThat(options, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.get(0); + assertThat(asyncapi.location, equalTo("mqtt/asyncapi.yaml")); + assertThat(asyncapi.asyncapi, instanceOf(Asyncapi.class)); + assertThat(options.mqttKafka.channels.sessions, equalTo("sessionsChannel")); + assertThat(options.mqttKafka.channels.messages, equalTo("messagesChannel")); + assertThat(options.mqttKafka.channels.retained, equalTo("retainedChannel")); + } + + @Test + public void shouldWriteOptionsMqttKafka() throws IOException + { + initJson("mqtt/asyncapi.yaml"); + List specs = new ArrayList<>(); + specs.add(new AsyncapiConfig("mqtt-api", 1, "mqtt/asyncapi.yaml", new Asyncapi())); + + + AsyncapiOptionsConfig options = AsyncapiOptionsConfig.builder() + .inject(Function.identity()) + .specs(specs) + .mqttKafka(AsyncapiMqttKafkaConfig.builder().channels(AsyncapiChannelsConfig.builder() + .sessions("sessionsChannel") + .messages("messagesChannel") + .retained("retainedChannel") + .build()) + .build()) + .build(); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"specs\":" + + "{" + + "\"mqtt-api\":\"mqtt/asyncapi.yaml\"" + + "}," + + "\"mqtt_kafka\":" + + "{" + + "\"channels\":" + + "{" + + "\"sessions\":\"sessionsChannel\"," + + "\"messages\":\"messagesChannel\"," + + "\"retained\":\"retainedChannel\"" + + "}" + + "}" + + "}")); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java new file mode 100644 index 0000000000..fb8b93e070 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiWithConfigAdapterTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class AsyncapiWithConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new AsyncapiWithConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadWith() + { + String text = "{\"api-id\":\"test\",\"operation-id\":\"testOperation\"}"; + + AsyncapiWithConfig with = jsonb.fromJson(text, AsyncapiWithConfig.class); + + assertThat(with, not(nullValue())); + assertThat(with.apiId, equalTo("test")); + } + + @Test + public void shouldWriteWith() + { + AsyncapiWithConfig with = new AsyncapiWithConfig("test", "testOperation"); + + String text = jsonb.toJson(with); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo("{\"api-id\":\"test\",\"operation-id\":\"testOperation\"}")); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java new file mode 100644 index 0000000000..1579c36616 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/client/AsyncapiIT.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.client; + +import static io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiConfigurationTest.ASYNCAPI_TARGET_ROUTE_ID_NAME; +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.ScriptProperty; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http") + .addScriptRoot("kafka", "io/aklivity/zilla/specs/binding/asyncapi/streams/kafka") + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") + .external("mqtt0") + .external("http0") + .external("kafka0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.mqtt.yaml") + @Specification({ + "${asyncapi}/mqtt/publish.and.subscribe/client", + "${mqtt}/publish.and.subscribe/server" + }) + @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967298") + @ScriptProperty("serverAddress \"zilla://streams/mqtt0\"") + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("client.http.yaml") + @Specification({ + "${asyncapi}/http/create.pet/client", + "${http}/create.pet/server" + }) + @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967299") + @ScriptProperty("serverAddress \"zilla://streams/http0\"") + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("client.kafka.yaml") + @Specification({ + "${asyncapi}/kafka/produce.message/client", + "${kafka}/produce.message/server" + }) + @Configure(name = ASYNCAPI_TARGET_ROUTE_ID_NAME, value = "4294967300") + @ScriptProperty("serverAddress \"zilla://streams/kafka0\"") + public void shouldProduceMessage() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java new file mode 100644 index 0000000000..ce7ed7df1b --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/proxy/AsyncapiIT.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.proxy; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") + .external("asyncapi_kafka0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("proxy.mqtt.kafka.yaml") + @Specification({ + "${asyncapi}/proxy.mqtt.publish/client", + "${asyncapi}/proxy.kafka.publish/server" + }) + public void shouldPublish() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java new file mode 100644 index 0000000000..9fb0d52045 --- /dev/null +++ b/incubator/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.asyncapi.internal.stream.server; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("mqtt", "io/aklivity/zilla/specs/binding/asyncapi/streams/mqtt") + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/asyncapi/streams/http") + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .configurationRoot("io/aklivity/zilla/specs/binding/asyncapi/config") + .external("asyncapi0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.mqtt.yaml") + @Specification({ + "${mqtt}/publish.and.subscribe/client", + "${asyncapi}/mqtt/publish.and.subscribe/server" + }) + public void shouldPublishAndSubscribe() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.http.yaml") + @Specification({ + "${http}/create.pet/client", + "${asyncapi}/http/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-openapi-asyncapi.spec/COPYRIGHT b/incubator/binding-openapi-asyncapi.spec/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi-asyncapi.spec/LICENSE b/incubator/binding-openapi-asyncapi.spec/LICENSE new file mode 100644 index 0000000000..1184b83429 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. diff --git a/incubator/binding-openapi-asyncapi.spec/NOTICE b/incubator/binding-openapi-asyncapi.spec/NOTICE new file mode 100644 index 0000000000..f1570ef67e --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/NOTICE @@ -0,0 +1,28 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::incubator::binding-asyncapi.spec under The Apache Software License, Version 2.0 + zilla::incubator::binding-openapi.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-kafka.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-mqtt-kafka.spec under Aklivity Community License Agreement + zilla::specs::binding-mqtt.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi.spec/NOTICE.template b/incubator/binding-openapi-asyncapi.spec/NOTICE.template new file mode 100644 index 0000000000..67b08ca131 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/NOTICE.template @@ -0,0 +1,16 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi.spec/mvnw b/incubator/binding-openapi-asyncapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi-asyncapi.spec/mvnw.cmd b/incubator/binding-openapi-asyncapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi-asyncapi.spec/pom.xml b/incubator/binding-openapi-asyncapi.spec/pom.xml new file mode 100644 index 0000000000..deed770da7 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/pom.xml @@ -0,0 +1,181 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + binding-openapi-asyncapi.spec + zilla::incubator::binding-openapi-asyncapi.spec + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.96 + 1 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-asyncapi.spec + ${project.version} + + + ${project.groupId} + binding-openapi.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + + + ${project.groupId} + binding-kafka.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.specs.binding.openapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/openapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java b/incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java similarity index 64% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java rename to incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java index db09b44830..81bcc26c90 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceOverrideConfig.java +++ b/incubator/binding-openapi-asyncapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/OpenapiAsyncapiSpecs.java @@ -12,18 +12,11 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.specs.binding.openapi.asyncapi; -public final class HttpKafkaWithProduceOverrideConfig +public final class OpenapiAsyncapiSpecs { - public final String name; - public final String value; - - public HttpKafkaWithProduceOverrideConfig( - String name, - String value) + private OpenapiAsyncapiSpecs() { - this.name = name; - this.value = value; } } diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java b/incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..d88214430f --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/moditect/module-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +open module io.aklivity.zilla.specs.binding.openapi.asyncapi +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..cb5c82fd24 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1 @@ +io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions$Mapper diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml new file mode 100644 index 0000000000..f21b4b91e5 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/asyncapi/petstore.yaml @@ -0,0 +1,101 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +asyncapi: 3.0.0 +info: + title: Petstore Kafka API + version: 1.0.0 +defaultContentType: application/json +servers: + host-connections: + host: 'localhost:9092' + protocol: kafka-secure + description: Test broker + tags: + - name: 'kind:remote' + description: This server is a remote server. Not exposed by the application. + - name: 'visibility:private' + description: This resource is private and only available to certain users. +channels: + petstore: + address: 'petstore' + messages: + pet: + $ref: '#/components/messages/pet' + description: The topic on which pet values may be produced and consumed. +operations: + listPets: + action: receive + channel: + $ref: '#/channels/petstore' + summary: >- + List all pets. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/petstore/messages/pet' + createPets: + action: send + channel: + $ref: '#/channels/petstore' + summary: >- + Create a pet. + traits: + - $ref: '#/components/operationTraits/kafka' + messages: + - $ref: '#/channels/petstore/messages/pet' +components: + messages: + pet: + name: Pet + title: Pet + summary: >- + Inform about Pet. + contentType: application/json + traits: + - $ref: '#/components/messageTraits/commonHeaders' + payload: + $ref: '#/components/schemas/petPayload' + schemas: + petPayload: + type: object + properties: + id: + type: integer + minimum: 0 + description: Pet id. + name: + type: string + description: Pet name. + tag: + type: string + description: Tag. + messageTraits: + commonHeaders: + headers: + type: object + properties: + my-app-header: + type: integer + minimum: 0 + maximum: 100 + operationTraits: + kafka: + bindings: + kafka: + clientId: + type: string + enum: + - my-app-id diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml new file mode 100644 index 0000000000..8b2dc0b5f9 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/openapi/petstore.yaml @@ -0,0 +1,94 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:9090 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + responses: + '200': + description: A paged array of pets + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml new file mode 100644 index 0000000000..c07f511c9e --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/proxy.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +bindings: + composite0: + type: openapi-asyncapi + kind: proxy + options: + specs: + openapi: + openapi-id: openapi/petstore.yaml + asyncapi: + asyncapi-id: asyncapi/petstore.yaml + routes: + - when: + - api-id: openapi-id + exit: asyncapi_client0 + with: + api-id: asyncapi-id diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json new file mode 100644 index 0000000000..c05fb5b9cd --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json @@ -0,0 +1,145 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "openapi-asyncapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "openapi-asyncapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "openapi-asyncapi" + }, + "kind": + { + "enum": [ "proxy"] + }, + "vault": false, + "options": + { + "properties": + { + "specs": + { + "title": "Specs", + "type": "object", + "properties": + { + "openapi": + { + "title": "Openapi", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 + }, + "asyncapi": + { + "title": "Asyncapi", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 + } + }, + "required": [ "openapi", "asyncapi" ] + } + }, + "additionalProperties": false + }, + "routes": + { + "items": + { + "properties": + { + "when": + { + "items": + { + "properties": + { + "api-id": + { + "title": "ApiId", + "type": "string" + }, + "operation-id": + { + "title": "OperationId", + "type": "string" + } + }, + "additionalProperties": false + } + }, + "with": + { + "properties": + { + "api-id": + { + "title": "ApiId", + "type": "string" + }, + "operation-id": + { + "title": "OperationId", + "type": "string" + } + } + } + }, + "required": + [ + "with" + ] + } + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "required": + [ + "routes" + ] + } + ] + } + } + } +] diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt new file mode 100644 index 0000000000..b6d858e440 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/client.rpt @@ -0,0 +1,60 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +connect "zilla://streams/asyncapi_client0" + option zilla:window 8192 + option zilla:transmission "duplex" + +write zilla:begin.ext ${asyncapi:beginEx() + .typeId(zilla:id("asyncapi")) + .apiId(1977467119) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("petstore") + .partition(-1, -2) + .ackMode("IN_SYNC_REPLICAS") + .build() + .build()) + .build()} + +connected + +write option zilla:flags "init" +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .timestamp(newTimestamp) + .partition(-1, -1) + .build() + .build()} +write zilla:data.empty +write flush + +write option zilla:flags "none" +write "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" +write flush + +write option zilla:flags "fin" +write zilla:data.empty +write flush + +write close +read closed diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt new file mode 100644 index 0000000000..d0ae5c6302 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi/create.pet/server.rpt @@ -0,0 +1,55 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +accept "zilla://streams/asyncapi_client0" + option zilla:window 8192 + option zilla:transmission "duplex" + +accepted + +read zilla:begin.ext ${asyncapi:matchBeginEx() + .typeId(zilla:id("asyncapi")) + .apiId(1977467119) + .extension(kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("PRODUCE_ONLY") + .topic("petstore") + .partition(-1, -2) + .ackMode("IN_SYNC_REPLICAS") + .build() + .build()) + .build()} + +connected + +read option zilla:flags "init" +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .produce() + .partition(-1, -1) + .build() + .build()} +read zilla:data.empty + +read option zilla:flags "none" +read "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" + +read option zilla:flags "fin" +read zilla:data.empty + +read closed +write close diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt new file mode 100644 index 0000000000..bb5f2f69d8 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/client.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} +connected + +write "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${openapi:matchBeginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .build()) + .build()} diff --git a/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt new file mode 100644 index 0000000000..cc1f7e4d9e --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi/create.pet/server.rpt @@ -0,0 +1,50 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +accept "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} + +connected + +read "{\"id\": 1, \"name\": \"Dog\", \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .apiId(1807802422) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "204") + .build()) + .build()} +write flush diff --git a/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java new file mode 100644 index 0000000000..44c1f57cf1 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/config/SchemaTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.asyncapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/openapi/asyncapi/" + + "schema/openapi.asyncapi.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/asyncapi/config"); + + + @Test + public void shouldValidateProxy() + { + JsonObject config = schema.validate("proxy.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java new file mode 100644 index 0000000000..a4c5f29642 --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/AsyncapiIT.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class AsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${asyncapi}/create.pet/client", + "${asyncapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java new file mode 100644 index 0000000000..a655b0dcfa --- /dev/null +++ b/incubator/binding-openapi-asyncapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/OpenapiIT.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.asyncapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class OpenapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${openapi}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi-asyncapi/COPYRIGHT b/incubator/binding-openapi-asyncapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi-asyncapi/LICENSE b/incubator/binding-openapi-asyncapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-openapi-asyncapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-openapi-asyncapi/NOTICE b/incubator/binding-openapi-asyncapi/NOTICE new file mode 100644 index 0000000000..9a20dc7d2a --- /dev/null +++ b/incubator/binding-openapi-asyncapi/NOTICE @@ -0,0 +1,35 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + ICU4J under Unicode/ICU License + Jackson-annotations under The Apache Software License, Version 2.0 + Jackson-core under The Apache Software License, Version 2.0 + jackson-databind under The Apache Software License, Version 2.0 + Jackson-dataformat-YAML under The Apache Software License, Version 2.0 + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + JSR 353 (JSON Processing) Default Provider under Dual license consisting of the CDDL v1.1 and GPL v2 + org.leadpony.justify under The Apache Software License, Version 2.0 + SnakeYAML under Apache License, Version 2.0 + zilla::incubator::binding-asyncapi under Aklivity Community License Agreement + zilla::incubator::binding-openapi under Aklivity Community License Agreement + zilla::incubator::catalog-inline under Aklivity Community License Agreement + zilla::incubator::model-core under Aklivity Community License Agreement + zilla::incubator::model-json under Aklivity Community License Agreement + zilla::runtime::binding-http under The Apache Software License, Version 2.0 + zilla::runtime::binding-http-kafka under Aklivity Community License Agreement + zilla::runtime::binding-kafka under The Apache Software License, Version 2.0 + zilla::runtime::binding-tcp under The Apache Software License, Version 2.0 + zilla::runtime::binding-tls under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi/NOTICE.template b/incubator/binding-openapi-asyncapi/NOTICE.template new file mode 100644 index 0000000000..67b08ca131 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/NOTICE.template @@ -0,0 +1,16 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi-asyncapi/mvnw b/incubator/binding-openapi-asyncapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi-asyncapi/mvnw.cmd b/incubator/binding-openapi-asyncapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi-asyncapi/pom.xml b/incubator/binding-openapi-asyncapi/pom.xml new file mode 100644 index 0000000000..e717f0f0de --- /dev/null +++ b/incubator/binding-openapi-asyncapi/pom.xml @@ -0,0 +1,284 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + binding-openapi-asyncapi + zilla::incubator::binding-openapi-asyncapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-openapi-asyncapi.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-openapi + ${project.version} + + + io.aklivity.zilla + binding-asyncapi + ${project.version} + + + io.aklivity.zilla + binding-http-kafka + ${project.version} + + + io.aklivity.zilla + binding-http + ${project.version} + + + io.aklivity.zilla + binding-kafka + ${project.version} + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http kafka asyncapi openapi + io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-openapi-asyncapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/openapi/asyncapi/\E + io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/ + + + + + io/aklivity/zilla/specs/binding/openapi/asyncapi/schema/openapi.asyncapi.schema.patch.json + ${project.build.directory}/classes + + + + unpack-openapi + generate-sources + + unpack + + + + + ${project.groupId} + binding-openapi-asyncapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java new file mode 100644 index 0000000000..7644d177ea --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/AsyncapiConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; + +public class AsyncapiConfig +{ + public final String apiLabel; + public final long apiId; + public final String location; + public final Asyncapi asyncapi; + + public AsyncapiConfig( + String apiLabel, + long apiId, + String location, + Asyncapi asyncapi) + { + this.apiLabel = apiLabel; + this.apiId = apiId; + this.location = location; + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java new file mode 100644 index 0000000000..82b9c9ff68 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiOptionsConfig.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenapiAsyncapiOptionsConfig extends OptionsConfig +{ + public final OpenapiAsyncapiSpecConfig specs; + + public OpenapiAsyncapiOptionsConfig( + OpenapiAsyncapiSpecConfig specs) + { + this.specs = specs; + } + + public long resolveOpenapiApiId( + String apiLabel) + { + long apiId = -1; + for (OpenapiConfig c : specs.openapi) + { + if (c.apiLabel.equals(apiLabel)) + { + apiId = c.apiId; + break; + } + } + return apiId; + } + + public long resolveAsyncapiApiId( + String apiLabel) + { + long apiId = -1; + for (AsyncapiConfig c : specs.asyncapi) + { + if (c.apiLabel.equals(apiLabel)) + { + apiId = c.apiId; + break; + } + } + return apiId; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java new file mode 100644 index 0000000000..da9b2ba82d --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiAsyncapiSpecConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + +import java.util.Set; + +public class OpenapiAsyncapiSpecConfig +{ + public final Set openapi; + public final Set asyncapi; + + public OpenapiAsyncapiSpecConfig( + Set openapi, + Set asyncapi) + { + this.openapi = openapi; + this.asyncapi = asyncapi; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java new file mode 100644 index 0000000000..5def7dce71 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/config/OpenapiConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; + +public class OpenapiConfig +{ + public final String apiLabel; + public final long apiId; + public final String location; + public final Openapi openapi; + + public OpenapiConfig( + String apiLabel, + long apiId, + String location, + Openapi openapi) + { + this.apiLabel = apiLabel; + this.apiId = apiId; + this.location = location; + this.openapi = openapi; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java new file mode 100644 index 0000000000..ae3a0b6784 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiAsyncapiBinding implements Binding +{ + public static final String NAME = "openapi-asyncapi"; + + private final OpenapiAsyncapiConfiguration config; + + OpenapiAsyncapiBinding( + OpenapiAsyncapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/openapi.asyncapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public OpenapiAsyncapiBindingContext supply( + EngineContext context) + { + return new OpenapiAsyncapiBindingContext(config, context); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java new file mode 100644 index 0000000000..85d5747da1 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingContext.java @@ -0,0 +1,74 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams.OpenapiAsyncapiProxyFactory; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams.OpenapiAsyncapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class OpenapiAsyncapiBindingContext implements BindingContext +{ + private final Map factories; + + OpenapiAsyncapiBindingContext( + OpenapiAsyncapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(PROXY, new OpenapiAsyncapiProxyFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + OpenapiAsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + OpenapiAsyncapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java new file mode 100644 index 0000000000..5a9df3c110 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class OpenapiAsyncapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public OpenapiAsyncapiBinding create( + Configuration config) + { + return new OpenapiAsyncapiBinding(new OpenapiAsyncapiConfiguration(config)); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java new file mode 100644 index 0000000000..3ebcbcf0e5 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/OpenapiAsyncapiConfiguration.java @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class OpenapiAsyncapiConfiguration extends Configuration +{ + private static final ConfigurationDef OPENAPI_ASYNC_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.openapi.asyncapi"); + OPENAPI_ASYNC_CONFIG = config; + } + + public OpenapiAsyncapiConfiguration( + Configuration config) + { + super(OPENAPI_ASYNC_CONFIG, config); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java new file mode 100644 index 0000000000..81344c31f0 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncCompositeBindingAdapter.java @@ -0,0 +1,211 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.PROXY; +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.AsyncapiOperation; +import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiChannelView; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaConditionConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponse; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiSchemaView; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; + +public final class OpenapiAsyncCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + + private final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiAsyncapiOptionsConfig options = (OpenapiAsyncapiOptionsConfig) binding.options; + + List routes = binding.routes.stream() + .map(r -> new OpenapiAsyncapiRouteConfig(r, options::resolveOpenapiApiId)) + .collect(toList()); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/http_kafka", binding.qname)) + .binding() + .name("http_kafka0") + .type("http-kafka") + .kind(PROXY) + .inject(b -> this.injectHttpKafkaRoutes(b, binding.qname, options.specs, routes)) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectHttpKafkaRoutes( + BindingConfigBuilder binding, + String qname, + OpenapiAsyncapiSpecConfig spec, + List routes) + { + for (OpenapiAsyncapiRouteConfig route : routes) + { + for (OpenapiAsyncapiConditionConfig condition : route.when) + { + Openapi openapi = spec.openapi.stream() + .filter(o -> o.apiLabel.equals(condition.apiId)) + .findFirst() + .get().openapi; + Asyncapi asyncapi = spec.asyncapi.stream() + .filter(o -> o.apiLabel.equals(route.with.apiId)) + .findFirst() + .get().asyncapi; + + for (String item : openapi.paths.keySet()) + { + OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(item)); + for (String method : path.methods().keySet()) + { + final String operationId = condition.operationId != null ? + condition.operationId : path.methods().get(method).operationId; + + final OpenapiOperation openapiOperation = path.methods().get(method); + + final AsyncapiOperation asyncapiOperation = asyncapi.operations.entrySet().stream() + .filter(f -> f.getKey().equals(operationId)) + .map(v -> v.getValue()) + .findFirst() + .get(); + + final AsyncapiChannelView channel = AsyncapiChannelView + .of(asyncapi.channels, asyncapiOperation.channel); + + binding + .route() + .exit(qname) + .when(HttpKafkaConditionConfig::builder) + .method(method) + .path(item) + .build() + .inject(r -> injectHttpKafkaRouteWith(r, openapi, openapiOperation, + asyncapiOperation.action, channel.address())) + .build(); + } + } + } + } + + return binding; + } + + private RouteConfigBuilder injectHttpKafkaRouteWith( + RouteConfigBuilder route, + Openapi openapi, + OpenapiOperation operation, + String action, + String address) + { + final HttpKafkaWithConfigBuilder newWith = HttpKafkaWithConfig.builder(); + + switch (action) + { + case "receive": + newWith.fetch(HttpKafkaWithFetchConfig.builder() + .topic(address) + .inject(with -> this.injectHttpKafkaRouteFetchWith(with, openapi, operation)) + .build()); + break; + case "send": + newWith.produce(HttpKafkaWithProduceConfig.builder() + .topic(address) + .acks("in_sync_replicas") + .build()); + break; + } + + route.with(newWith.build()); + + return route; + } + + private HttpKafkaWithFetchConfigBuilder injectHttpKafkaRouteFetchWith( + HttpKafkaWithFetchConfigBuilder fetch, + Openapi openapi, + OpenapiOperation operation) + { + merge: + for (Map.Entry response : operation.responses.entrySet()) + { + OpenapiSchemaView schema = resolveSchemaForJsonContentType(response.getValue().content, openapi); + + if ("array".equals(schema.getType())) + { + fetch.merged(HttpKafkaWithFetchMergeConfig.builder() + .contentType("application/json") + .build()); + break merge; + } + + } + return fetch; + } + + private OpenapiSchemaView resolveSchemaForJsonContentType( + Map content, + Openapi openApi) + { + OpenapiResponse response = null; + if (content != null) + { + for (String contentType : content.keySet()) + { + if (jsonContentType.reset(contentType).matches()) + { + response = content.get(contentType); + break; + } + } + } + + return response == null ? null : OpenapiSchemaView.of(openApi.components.schemas, response.schema); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java new file mode 100644 index 0000000000..9162fe0510 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiBindingConfig.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import java.util.List; + +import org.agrona.collections.IntHashSet; +import org.agrona.collections.Long2LongHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiAsyncapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final OpenapiAsyncapiOptionsConfig options; + public final List routes; + + private final IntHashSet httpKafkaOrigins; + private final Long2LongHashMap resolvedIds; + + public OpenapiAsyncapiBindingConfig( + BindingConfig binding) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.options = OpenapiAsyncapiOptionsConfig.class.cast(binding.options); + + this.routes = binding.routes.stream() + .map(r -> new OpenapiAsyncapiRouteConfig(r, options::resolveOpenapiApiId)) + .collect(toList()); + + this.resolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http-kafka")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(options.specs.openapi.stream().findFirst().get().apiId, r.id), + (m, r) -> m, + IDENTITY_FINISH + )); + + this.httpKafkaOrigins = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http-kafka")) + .map(b -> NamespacedId.namespaceId(b.id)) + .collect(toCollection(IntHashSet::new)); + } + + public boolean isCompositeNamespace( + int namespaceId) + { + return httpKafkaOrigins.contains(namespaceId); + } + + public long resolveResolvedId( + long apiId) + { + return resolvedIds.get(apiId); + } + + public OpenapiAsyncapiRouteConfig resolve( + long authorization, + long apiId) + { + return routes.stream() + .filter(r -> r.authorized(authorization) && r.matches(apiId)) + .findFirst() + .orElse(null); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java new file mode 100644 index 0000000000..21aeb203ac --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfig.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; + +public class OpenapiAsyncapiConditionConfig extends ConditionConfig +{ + public final String apiId; + public final String operationId; + + public OpenapiAsyncapiConditionConfig( + String apiId, + String operationId) + { + this.apiId = apiId; + this.operationId = operationId; + } + + public boolean matches( + long apiId, + Function supplyApiId) + { + return matchesApiId(apiId, supplyApiId); + } + + private boolean matchesApiId( + long apiId, + Function supplyApiId) + { + return supplyApiId.apply(this.apiId) == apiId; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java new file mode 100644 index 0000000000..83e9358679 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi; + +public final class OpenapiAsyncapiConditionConfigAdapter implements ConditionConfigAdapterSpi, + JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + ConditionConfig condition) + { + OpenapiAsyncapiConditionConfig asyncapiCondition = (OpenapiAsyncapiConditionConfig) condition; + JsonObjectBuilder object = Json.createObjectBuilder(); + + object.add(API_ID_NAME, asyncapiCondition.apiId); + + if (asyncapiCondition.operationId != null) + { + object.add(OPERATION_ID_NAME, asyncapiCondition.operationId); + } + + return object.build(); + } + + @Override + public ConditionConfig adaptFromJson( + JsonObject object) + { + String apiId = object.containsKey(API_ID_NAME) + ? object.getString(API_ID_NAME) + : null; + + String operationId = object.containsKey(OPERATION_ID_NAME) + ? object.getString(OPERATION_ID_NAME) + : null; + + return new OpenapiAsyncapiConditionConfig(apiId, operationId); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..d76e26b215 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapter.java @@ -0,0 +1,138 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.unmodifiableSet; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Function; +import java.util.zip.CRC32C; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonString; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiParser; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiParser; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class OpenapiAsyncapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String OPENAPI_NAME = "openapi"; + private static final String ASYNCAPI_NAME = "asyncapi"; + private static final String SPECS_NAME = "specs"; + + private final CRC32C crc; + + private final OpenapiParser openapiParser = new OpenapiParser(); + private final AsyncapiParser asyncapiParser = new AsyncapiParser(); + + private Function readURL; + + public OpenapiAsyncapiOptionsConfigAdapter() + { + crc = new CRC32C(); + } + + @Override + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + OpenapiAsyncapiOptionsConfig proxyOptions = (OpenapiAsyncapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + JsonObjectBuilder spec = Json.createObjectBuilder(); + + JsonObjectBuilder openapi = Json.createObjectBuilder(); + proxyOptions.specs.openapi.forEach(o -> openapi.add(o.apiLabel, o.location)); + spec.add(OPENAPI_NAME, openapi); + + JsonObjectBuilder asyncapi = Json.createObjectBuilder(); + proxyOptions.specs.asyncapi.forEach(a -> asyncapi.add(a.apiLabel, a.location)); + spec.add(ASYNCAPI_NAME, asyncapi); + + object.add(SPECS_NAME, spec); + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + JsonObject specs = object.getJsonObject(SPECS_NAME); + + JsonObject openapi = specs.getJsonObject(OPENAPI_NAME); + Set openapis = new LinkedHashSet<>(); + openapi.forEach((n, v) -> + { + final String location = JsonString.class.cast(v).getString(); + final String specText = readURL.apply(location); + final String apiLabel = n; + crc.reset(); + crc.update(specText.getBytes(UTF_8)); + final long apiId = crc.getValue(); + openapis.add(new OpenapiConfig(apiLabel, apiId, location, openapiParser.parse(specText))); + }); + + JsonObject asyncapi = specs.getJsonObject(ASYNCAPI_NAME); + Set asyncapis = new LinkedHashSet<>(); + asyncapi.forEach((n, v) -> + { + final String location = JsonString.class.cast(v).getString(); + final String specText = readURL.apply(location); + final String apiLabel = n; + crc.reset(); + crc.update(specText.getBytes(UTF_8)); + final long apiId = crc.getValue(); + asyncapis.add(new AsyncapiConfig(apiLabel, apiId, location, asyncapiParser.parse(specText))); + }); + + OpenapiAsyncapiSpecConfig specConfig = new OpenapiAsyncapiSpecConfig( + unmodifiableSet(openapis), unmodifiableSet(asyncapis)); + + return new OpenapiAsyncapiOptionsConfig(specConfig); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java new file mode 100644 index 0000000000..fa1982fc31 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiRouteConfig.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.util.stream.Collectors.toList; + +import java.util.List; +import java.util.function.Function; +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class OpenapiAsyncapiRouteConfig +{ + public final long id; + public final OpenapiAsyncapiWithConfig with; + private final Function supplyApiId; + public final List when; + private final LongPredicate authorized; + + public OpenapiAsyncapiRouteConfig( + RouteConfig route, + Function supplyApiId) + { + this.id = route.id; + this.authorized = route.authorized; + this.when = route.when.stream() + .map(OpenapiAsyncapiConditionConfig.class::cast) + .collect(toList()); + this.with = (OpenapiAsyncapiWithConfig) route.with; + this.supplyApiId = supplyApiId; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } + + boolean matches( + long apiId) + { + return when.isEmpty() || when.stream().anyMatch(m -> m.matches(apiId, supplyApiId)); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java new file mode 100644 index 0000000000..a370ec4f05 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfig.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public class OpenapiAsyncapiWithConfig extends WithConfig +{ + public final String apiId; + public final String operationId; + + public OpenapiAsyncapiWithConfig( + String apiId, + String operationId) + { + this.apiId = apiId; + this.operationId = operationId; + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java new file mode 100644 index 0000000000..4221465e8b --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapter.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBinding; +import io.aklivity.zilla.runtime.engine.config.WithConfig; +import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; + +public class OpenapiAsyncapiWithConfigAdapter implements WithConfigAdapterSpi, JsonbAdapter +{ + private static final String API_ID_NAME = "api-id"; + private static final String OPERATION_ID_NAME = "operation-id"; + + @Override + public String type() + { + return OpenapiAsyncapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + WithConfig with) + { + OpenapiAsyncapiWithConfig config = (OpenapiAsyncapiWithConfig) with; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (config.apiId != null) + { + object.add(API_ID_NAME, config.apiId); + } + + if (config.operationId != null) + { + object.add(OPERATION_ID_NAME, config.operationId); + } + + return object.build(); + } + + @Override + public WithConfig adaptFromJson( + JsonObject object) + { + String apiId = object.containsKey(API_ID_NAME) + ? object.getString(API_ID_NAME) + : null; + + String operationId = object.containsKey(OPERATION_ID_NAME) + ? object.getString(OPERATION_ID_NAME) + : null; + + return new OpenapiAsyncapiWithConfig(apiId, operationId); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java new file mode 100644 index 0000000000..025b961fa3 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiProxyFactory.java @@ -0,0 +1,1762 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +import java.util.function.Function; +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.AsyncapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiRouteConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.AsyncapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiAsyncapiProxyFactory implements OpenapiAsyncapiStreamFactory +{ + private static final int UNKNOWN_COMPOSITE_RESOLVED_ID = -1; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final AsyncapiBeginExFW asyncapiBeginExRO = new AsyncapiBeginExFW(); + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + + private final AsyncapiBeginExFW.Builder asyncapiBeginExRW = new AsyncapiBeginExFW.Builder(); + private final OpenapiBeginExFW.Builder openapiBeginExRW = new OpenapiBeginExFW.Builder(); + + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Function supplyTypeId; + private final Long2ObjectHashMap bindings; + private final Long2LongHashMap apiIds; + private final int openapiTypeId; + private final int asyncapiTypeId; + + private final OpenapiAsyncapiConfiguration config; + + public OpenapiAsyncapiProxyFactory( + OpenapiAsyncapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTypeId = context::supplyTypeId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.apiIds = new Long2LongHashMap(-1); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.asyncapiTypeId = context.supplyTypeId(AsyncapiBinding.NAME); + } + + + @Override + public int routedTypeId() + { + return asyncapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiAsyncapiBindingConfig apiBinding = new OpenapiAsyncapiBindingConfig(binding); + bindings.put(binding.id, apiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + + final OpenapiAsyncapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + if (!binding.isCompositeNamespace(NamespacedId.namespaceId(originId))) + { + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + final long apiId = openapiBeginEx.apiId(); + final String operationId = openapiBeginEx.operationId().asString(); + + final long compositeResolvedId = binding.resolveResolvedId(apiId); + apiIds.put(apiId, apiId); + + if (compositeResolvedId != UNKNOWN_COMPOSITE_RESOLVED_ID) + { + newStream = new OpenapiStream( + receiver, + originId, + routedId, + initialId, + apiId, + authorization, + compositeResolvedId, + operationId)::onOpenapiMessage; + } + } + else + { + final long apiId = apiIds.get(affinity); + final OpenapiAsyncapiRouteConfig route = binding.resolve(authorization, apiId); + + if (route != null) + { + final long clientApiId = binding.options.resolveAsyncapiApiId(route.with.apiId); + final String operationId = route.with.operationId; + newStream = new CompositeClientStream( + receiver, + originId, + routedId, + initialId, + authorization, + route.id, + affinity, + clientApiId, + operationId)::onCompositeClientMessage; + } + } + } + + return newStream; + } + + private final class OpenapiStream + { + private final CompositeServerStream delegate; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private OpenapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long compositeResolvedId, + String operationId) + { + this.delegate = + new CompositeServerStream(this, compositeResolvedId, compositeResolvedId, authorization, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenpaiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenpaiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiAsyncapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + delegate.doCompositeBegin(traceId, affinity, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onOpenpaiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + + assert initialAck <= initialSeq; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeAbort(traceId, extension); + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + state = OpenapiAsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doOpenapiWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = delegate.initialAck; + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + Flyweight extension) + { + state = OpenapiAsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doOpenapiData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBud, reserved, extension); + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiAsyncapiState.replyOpening(state) && !OpenapiAsyncapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiAsyncapiState.closeReply(state); + } + + private void doOpenapiAbort( + long traceId) + { + if (OpenapiAsyncapiState.replyOpening(state) && !OpenapiAsyncapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiAsyncapiState.closeInitial(state); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiAsyncapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onOpenpaiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeWindow(traceId, acknowledge, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doOpenapiReset(traceId); + doOpenapiAbort(traceId); + + delegate.cleanup(traceId); + } + } + + final class CompositeServerStream + { + private final String operationId; + private final long originId; + private final long routedId; + private final long authorization; + + private OpenapiStream delegate; + private long initialId; + private long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeServerStream( + OpenapiStream delegate, + long routedId, + long compositeResolvedId, + long authorization, + String operationId) + { + this.delegate = delegate; + this.originId = routedId; + this.routedId = compositeResolvedId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onCompositeServerMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiAsyncapiState.openReply(state); + + final OpenapiBeginExFW openapiBeginEx = openapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .apiId(delegate.affinity) + .operationId(operationId) + .extension(extension) + .build(); + + delegate.doOpenapiBegin(traceId, openapiBeginEx); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiData(traceId, flags, reserved, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiAbort(traceId); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiAsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doOpenapiReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiAsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doOpenapiWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiAsyncapiState.closeReply(state); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + long affinity, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialOpening(state)) + { + assert state == 0; + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.receiver = newStream(this::onCompositeServerMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, affinity, extension); + state = OpenapiAsyncapiState.openingInitial(state); + } + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class CompositeClientStream + { + private final long originId; + private final long routedId; + private final MessageConsumer sender; + private final long affinity; + private final long authorization; + private final AsyncapiStream delegate; + + private long initialId; + private long replyId; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private CompositeClientStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long authorization, + long resolvedId, + long affinity, + long apiId, + String operationId) + { + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.delegate = new AsyncapiStream(this, originId, resolvedId, authorization, apiId, operationId); + } + + private void onCompositeClientMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onCompositeBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onCompositeData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onCompositeFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onCompositeEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onCompositeAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onCompositeReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onCompositeWindow(window); + break; + default: + break; + } + } + + private void onCompositeBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + + state = OpenapiAsyncapiState.openingInitial(state); + + delegate.doAsyncapiBegin(traceId, extension); + } + + private void onCompositeData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final int flags = data.flags(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onCompositeFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + delegate.doAsyncapiFlush(traceId, reserved, extension); + } + + private void onCompositeEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiEnd(traceId, extension); + } + + private void onCompositeAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiAsyncapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + delegate.doAsyncapiAbort(traceId, EMPTY_OCTETS); + } + + private void onCompositeReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + + replyAck = acknowledge; + + state = OpenapiAsyncapiState.closeReply(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doAsyncapiReset(traceId); + } + + private void onCompositeWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = OpenapiAsyncapiState.openReply(state); + + assert replyAck <= replySeq; + + delegate.doAsyncapiWindow(authorization, traceId, budgetId, padding); + } + + private void doCompositeReset( + long traceId) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + state = OpenapiAsyncapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doCompositeWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + initialAck = Math.max(delegate.initialAck, 0); + initialMax = delegate.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doCompositeBegin( + long traceId, + OctetsFW extension) + { + replySeq = delegate.replySeq; + replyAck = delegate.replyAck; + replyMax = delegate.replyMax; + state = OpenapiAsyncapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doCompositeData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + replySeq += reserved; + + assert replySeq <= replyAck + replyMax; + } + + private void doCompositeFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doCompositeEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = OpenapiAsyncapiState.closeReply(state); + + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, traceId, authorization, extension); + } + } + + private void doCompositeAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + replySeq = delegate.replySeq; + state = OpenapiAsyncapiState.closeReply(state); + + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + } + + private void cleanup( + long traceId) + { + doCompositeAbort(traceId, EMPTY_OCTETS); + doCompositeReset(traceId); + } + } + + final class AsyncapiStream + { + private final long originId; + private final long routedId; + private final long authorization; + private final CompositeClientStream delegate; + private final long apiId; + + private long initialId; + private final String operationId; + private long replyId; + private MessageConsumer asyncapi; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private AsyncapiStream( + CompositeClientStream delegate, + long originId, + long routedId, + long authorization, + long apiId, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.operationId = operationId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + this.apiId = apiId; + } + + private void onAsyncapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onAsyncapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onAsyncapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onAsyncapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onAsyncapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onAsyncapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onAsyncapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onAsyncapiWindow(window); + break; + default: + break; + } + } + + private void onAsyncapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiAsyncapiState.openingReply(state); + + final AsyncapiBeginExFW asyncapiBeginEx = extension.get(asyncapiBeginExRO::tryWrap); + final OctetsFW asyncapiExtension = asyncapiBeginEx != null ? asyncapiBeginEx.extension() : EMPTY_OCTETS; + + delegate.doCompositeBegin(traceId, asyncapiExtension); + } + + private void onAsyncapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onAsyncapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doCompositeFlush(traceId, reserved, extension); + } + + private void onAsyncapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeEnd(traceId, extension); + } + + private void onAsyncapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiAsyncapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doCompositeAbort(traceId, EMPTY_OCTETS); + } + + private void onAsyncapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiAsyncapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doCompositeReset(traceId); + } + + + private void onAsyncapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiAsyncapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doCompositeWindow(authorization, traceId, budgetId, padding); + } + + private void doAsyncapiReset( + long traceId) + { + if (!OpenapiAsyncapiState.replyClosed(state)) + { + doReset(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiAsyncapiState.closeReply(state); + } + } + + private void doAsyncapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(asyncapi, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void doAsyncapiBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialOpening(state)) + { + assert state == 0; + + final AsyncapiBeginExFW asyncapiBeginEx = asyncapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(asyncapiTypeId) + .apiId(apiId) + .operationId(operationId) + .extension(extension) + .build(); + + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.asyncapi = newStream(this::onAsyncapiMessage, + originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, 0L, asyncapiBeginEx); + state = OpenapiAsyncapiState.openingInitial(state); + } + } + + private void doAsyncapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doAsyncapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + } + + private void doAsyncapiEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doEnd(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void doAsyncapiAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiAsyncapiState.initialClosed(state)) + { + doAbort(asyncapi, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiAsyncapiState.closeInitial(state); + } + } + + private void cleanup( + long traceId) + { + doAsyncapiAbort(traceId, EMPTY_OCTETS); + delegate.doCompositeReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java new file mode 100644 index 0000000000..2a20838e7b --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +public final class OpenapiAsyncapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENING = 0x01; + private static final int REPLY_OPENED = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return state | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + private OpenapiAsyncapiState() + { + // utility + } +} diff --git a/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java new file mode 100644 index 0000000000..d115a8e498 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface OpenapiAsyncapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java b/incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..f01ec71dfb --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/moditect/module-info.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.openapi.asyncapi +{ + requires org.leadpony.justify; + + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.asyncapi; + requires io.aklivity.zilla.runtime.binding.openapi; + requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.kafka; + requires io.aklivity.zilla.runtime.binding.http.kafka; + + exports io.aklivity.zilla.runtime.binding.openapi.asyncapi.config; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncCompositeBindingAdapter; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiOptionsConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiConditionConfigAdapter; + + provides io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiWithConfigAdapter; +} diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..2b0d43f82d --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.OpenapiAsyncapiBindingFactorySpi diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..ccb4b52815 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncCompositeBindingAdapter diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi new file mode 100644 index 0000000000..346243b60f --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiConditionConfigAdapter diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..af68b6a234 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiOptionsConfigAdapter diff --git a/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi new file mode 100644 index 0000000000..7b82979aff --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config.OpenapiAsyncapiWithConfigAdapter diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java new file mode 100644 index 0000000000..3dc345e468 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiConditionConfigAdapterTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertThat; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class OpenapiAsyncapiConditionConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new OpenapiAsyncapiConditionConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadCondition() + { + String text = + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"o-id\"" + + "}"; + + OpenapiAsyncapiConditionConfig condition = jsonb.fromJson(text, OpenapiAsyncapiConditionConfig.class); + + assertThat(condition, not(nullValue())); + assertThat(condition.apiId, equalTo("test")); + assertThat(condition.operationId, equalTo("o-id")); + } + + @Test + public void shouldWriteCondition() + { + OpenapiAsyncapiConditionConfig condition = new OpenapiAsyncapiConditionConfig("test", "o-id"); + + String text = jsonb.toJson(condition); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo( + "{" + + "\"api-id\":\"test\"," + + "\"operation-id\":\"o-id\"" + + "}")); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..9d24c5c86d --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiOptionsConfigAdapterTest.java @@ -0,0 +1,128 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Collections.singleton; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.AsyncapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiAsyncapiSpecConfig; +import io.aklivity.zilla.runtime.binding.openapi.asyncapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.openapi.asyncapi.OpenapiAsyncapiSpecs; + +public class OpenapiAsyncapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + initSpec("openapi/petstore.yaml"); + initSpec("asyncapi/petstore.yaml"); + } + + public void initSpec( + String specConfig) throws IOException + { + try (InputStream resource = OpenapiAsyncapiSpecs.class + .getResourceAsStream("config/" + specConfig)) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL(specConfig); + + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("openapi-asyncapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptions() + { + String text = + "{" + + " \"specs\": {" + + " \"openapi\": {" + + " \"openapi-id\": \"openapi/petstore.yaml\"" + + " }," + + " \"asyncapi\": {" + + " \"asyncapi-id\": \"asyncapi/petstore.yaml\"" + + " }" + + " }" + + "}"; + + OpenapiAsyncapiOptionsConfig options = jsonb.fromJson(text, OpenapiAsyncapiOptionsConfig.class); + assertThat(options, not(nullValue())); + OpenapiConfig openapi = options.specs.openapi.stream().findFirst().get(); + assertEquals("openapi-id", openapi.apiLabel); + assertThat(openapi.openapi, not(nullValue())); + AsyncapiConfig asyncapi = options.specs.asyncapi.stream().findFirst().get(); + assertEquals("asyncapi-id", asyncapi.apiLabel); + assertThat(asyncapi.asyncapi, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + String expected = "{\"specs\":{\"openapi\":{\"openapi-id\":\"openapi/petstore.yaml\"},\"asyncapi\":" + + "{\"asyncapi-id\":\"asyncapi/petstore.yaml\"}}}"; + + OpenapiConfig openapi = new OpenapiConfig("openapi-id", 0L, + "openapi/petstore.yaml", new Openapi()); + AsyncapiConfig asyncapi = new AsyncapiConfig("asyncapi-id", 0L, + "asyncapi/petstore.yaml", new Asyncapi()); + + final OpenapiAsyncapiOptionsConfig options = new OpenapiAsyncapiOptionsConfig( + new OpenapiAsyncapiSpecConfig(singleton(openapi), + singleton(asyncapi))); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertEquals(expected, text); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java new file mode 100644 index 0000000000..f82eebf73a --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncapiWithConfigAdapterTest.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class OpenapiAsyncapiWithConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new OpenapiAsyncapiWithConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadWith() + { + String text = "{\"api-id\":\"test\",\"operation-id\":\"o-id\"}"; + + OpenapiAsyncapiWithConfig with = jsonb.fromJson(text, OpenapiAsyncapiWithConfig.class); + + assertThat(with, not(nullValue())); + assertThat(with.apiId, equalTo("test")); + assertThat(with.operationId, equalTo("o-id")); + } + + @Test + public void shouldWriteWith() + { + OpenapiAsyncapiWithConfig with = new OpenapiAsyncapiWithConfig("test", "o-id"); + + String text = jsonb.toJson(with); + + assertThat(text, not(nullValue())); + assertThat(text, equalTo("{\"api-id\":\"test\",\"operation-id\":\"o-id\"}")); + } +} diff --git a/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java new file mode 100644 index 0000000000..2c28776b51 --- /dev/null +++ b/incubator/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.asyncapi.internal.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class OpenapiAsyncapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/openapi") + .addScriptRoot("asyncapi", "io/aklivity/zilla/specs/binding/openapi/asyncapi/streams/asyncapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/asyncapi/config") + .external("asyncapi_client0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("proxy.yaml") + @Specification({ + "${openapi}/create.pet/client", + "${asyncapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/binding-openapi.spec/COPYRIGHT b/incubator/binding-openapi.spec/COPYRIGHT new file mode 100644 index 0000000000..8b1b7215ef --- /dev/null +++ b/incubator/binding-openapi.spec/COPYRIGHT @@ -0,0 +1,13 @@ +Copyright ${copyrightYears} Aklivity Inc. + +Aklivity licenses this file to you 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: + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. diff --git a/incubator/binding-openapi.spec/LICENSE b/incubator/binding-openapi.spec/LICENSE new file mode 100644 index 0000000000..90fe2ef74f --- /dev/null +++ b/incubator/binding-openapi.spec/LICENSE @@ -0,0 +1,115 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. + diff --git a/incubator/binding-openapi.spec/NOTICE b/incubator/binding-openapi.spec/NOTICE new file mode 100644 index 0000000000..7b0a8539b0 --- /dev/null +++ b/incubator/binding-openapi.spec/NOTICE @@ -0,0 +1,23 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::binding-http.spec under The Apache Software License, Version 2.0 + zilla::specs::binding-proxy.spec under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi.spec/NOTICE.template b/incubator/binding-openapi.spec/NOTICE.template new file mode 100644 index 0000000000..67b08ca131 --- /dev/null +++ b/incubator/binding-openapi.spec/NOTICE.template @@ -0,0 +1,16 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi.spec/mvnw b/incubator/binding-openapi.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi.spec/mvnw.cmd b/incubator/binding-openapi.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi.spec/pom.xml b/incubator/binding-openapi.spec/pom.xml new file mode 100644 index 0000000000..f1bb993a8d --- /dev/null +++ b/incubator/binding-openapi.spec/pom.xml @@ -0,0 +1,166 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + binding-openapi.spec + zilla::incubator::binding-openapi.spec + + + + The Apache Software License, Version 2.0 + http://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 11 + 11 + 0.91 + 1 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.specs.binding.openapi.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/binding/openapi/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java new file mode 100644 index 0000000000..d06d407f29 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiFunctions.java @@ -0,0 +1,206 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi; + +import java.nio.ByteBuffer; +import java.util.Objects; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.kaazing.k3po.lang.el.BytesMatcher; +import org.kaazing.k3po.lang.el.Function; +import org.kaazing.k3po.lang.el.spi.FunctionMapperSpi; + +import io.aklivity.zilla.specs.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; + +public final class OpenapiFunctions +{ + @Function + public static OpenapiBeginExBuilder beginEx() + { + return new OpenapiBeginExBuilder(); + } + + @Function + public static OpenapiBeginExMatcherBuilder matchBeginEx() + { + return new OpenapiBeginExMatcherBuilder(); + } + + public static final class OpenapiBeginExBuilder + { + private final OpenapiBeginExFW.Builder beginExRW; + + private OpenapiBeginExBuilder() + { + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1024 * 8]); + this.beginExRW = new OpenapiBeginExFW.Builder().wrap(writeBuffer, 0, writeBuffer.capacity()); + } + + public OpenapiBeginExBuilder typeId( + int typeId) + { + beginExRW.typeId(typeId); + return this; + } + + public OpenapiBeginExBuilder apiId( + long apiId) + { + beginExRW.apiId(apiId); + return this; + } + + public OpenapiBeginExBuilder operationId( + String operationId) + { + beginExRW.operationId(operationId); + return this; + } + + public OpenapiBeginExBuilder extension( + byte[] extension) + { + beginExRW.extension(e -> e.set(extension)); + return this; + } + + public byte[] build() + { + final OpenapiBeginExFW beginEx = beginExRW.build(); + final byte[] array = new byte[beginEx.sizeof()]; + beginEx.buffer().getBytes(beginEx.offset(), array); + return array; + } + } + + public static final class OpenapiBeginExMatcherBuilder + { + private final DirectBuffer bufferRO = new UnsafeBuffer(); + + private final OpenapiBeginExFW beginExRO = new OpenapiBeginExFW(); + + private Integer typeId; + private Long apiId; + private String operationId; + private OctetsFW.Builder extensionRW; + + public OpenapiBeginExMatcherBuilder typeId( + int typeId) + { + this.typeId = typeId; + return this; + } + + public OpenapiBeginExMatcherBuilder operationId( + String operationId) + { + this.operationId = operationId; + return this; + } + + public OpenapiBeginExMatcherBuilder apiId( + long apiId) + { + this.apiId = apiId; + return this; + } + + public OpenapiBeginExMatcherBuilder extension( + byte[] extension) + { + assert extensionRW == null; + extensionRW = new OctetsFW.Builder().wrap(new UnsafeBuffer(new byte[1024]), 0, 1024); + + extensionRW.set(Objects.requireNonNullElseGet(extension, () -> new byte[] {})); + + return this; + } + + public BytesMatcher build() + { + return typeId != null ? this::match : buf -> null; + } + + private OpenapiBeginExFW match( + ByteBuffer byteBuf) throws Exception + { + if (!byteBuf.hasRemaining()) + { + return null; + } + + bufferRO.wrap(byteBuf); + final OpenapiBeginExFW beginEx = beginExRO.tryWrap(bufferRO, byteBuf.position(), byteBuf.limit()); + + if (beginEx != null && + matchTypeId(beginEx) && + matchApiId(beginEx) && + matchOperationId(beginEx) && + matchExtension(beginEx)) + { + byteBuf.position(byteBuf.position() + beginEx.sizeof()); + return beginEx; + } + throw new Exception(beginEx.toString()); + } + + private boolean matchTypeId( + OpenapiBeginExFW beginEx) + { + return typeId == beginEx.typeId(); + } + + private boolean matchApiId( + OpenapiBeginExFW beginEx) + { + return apiId == null || apiId == beginEx.apiId(); + } + + private boolean matchOperationId( + OpenapiBeginExFW beginEx) + { + return operationId == null || operationId.equals(beginEx.operationId().asString()); + } + + private boolean matchExtension( + final OpenapiBeginExFW beginEx) + { + return extensionRW == null || extensionRW.build().equals(beginEx.extension()); + } + } + + public static class Mapper extends FunctionMapperSpi.Reflective + { + public Mapper() + { + super(OpenapiFunctions.class); + } + + @Override + public String getPrefixName() + { + return "openapi"; + } + } + + private OpenapiFunctions() + { + // utility + } +} diff --git a/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java new file mode 100644 index 0000000000..3faa1eb489 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/java/io/aklivity/zilla/specs/binding/openapi/OpenapiSpecs.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi; + +public final class OpenapiSpecs +{ + private OpenapiSpecs() + { + } +} diff --git a/incubator/binding-openapi.spec/src/main/moditect/module-info.java b/incubator/binding-openapi.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..a85321fdd7 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/moditect/module-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +open module io.aklivity.zilla.specs.binding.openapi +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi new file mode 100644 index 0000000000..3504330305 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/services/org.kaazing.k3po.lang.el.spi.FunctionMapperSpi @@ -0,0 +1,2 @@ +io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions$Mapper + diff --git a/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl new file mode 100644 index 0000000000..8b845e5025 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/resources/META-INF/zilla/openapi.idl @@ -0,0 +1,28 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope openapi +{ + + scope stream + { + struct OpenapiBeginEx extends core::stream::Extension + { + int64 apiId = 0; + string16 operationId = null; + octets extension; + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml new file mode 100644 index 0000000000..fe9cddc73b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/client.yaml @@ -0,0 +1,28 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + openapi0: + type: openapi + kind: client + options: + tcp: + host: localhost + port: 8080 + specs: + openapi-id: openapi/petstore.yaml diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml new file mode 100644 index 0000000000..8885f2c6b9 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/openapi/petstore.yaml @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://localhost:8080 + - url: https://localhost:9090 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + maximum: 100 + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{id}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: id + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pet" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + type: object + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + maxItems: 100 + items: + $ref: "#/components/schemas/Pet" + Error: + type: object + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml new file mode 100644 index 0000000000..0fe20d21c1 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server-secure.yaml @@ -0,0 +1,45 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +vaults: + server: + type: test +bindings: + composite0: + type: openapi + kind: server + vault: server + options: + tls: + keys: + - localhost + alpn: + - protocol2 + http: + authorization: + test0: + credentials: + cookies: + access_token: "{credentials}" + headers: + authorization: Bearer {credentials} + query: + access_token: "{credentials}" + specs: + openapi-id: openapi/petstore.yaml + exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml new file mode 100644 index 0000000000..07ad11ac12 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/config/server.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +bindings: + composite0: + type: openapi + kind: server + options: + specs: + openapi-id: openapi/petstore.yaml + exit: openapi0 diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json new file mode 100644 index 0000000000..6a175a4f63 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json @@ -0,0 +1,1666 @@ +{ + "id": "https://spec.openapis.org/oas/3.0/schema/2021-09-28", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "The description of OpenAPI v3.0.x documents, as defined by https://spec.openapis.org/oas/v3.0.3", + "type": "object", + "required": [ + "openapi", + "info", + "paths" + ], + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.0\\.\\d(-.+)?$" + }, + "info": { + "$ref": "#/definitions/Info" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/Tag" + }, + "uniqueItems": true + }, + "paths": { + "$ref": "#/definitions/Paths" + }, + "components": { + "$ref": "#/definitions/Components" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "definitions": { + "Reference": { + "type": "object", + "required": [ + "$ref" + ], + "patternProperties": { + "^\\$ref$": { + "type": "string", + "format": "uri-reference" + } + } + }, + "Info": { + "type": "object", + "required": [ + "title", + "version" + ], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri-reference" + }, + "contact": { + "$ref": "#/definitions/Contact" + }, + "license": { + "$ref": "#/definitions/License" + }, + "version": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Contact": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "License": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Server": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/ServerVariable" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ServerVariable": { + "type": "object", + "required": [ + "default" + ], + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + } + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Components": { + "type": "object", + "properties": { + "schemas": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "responses": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Response" + } + ] + } + } + }, + "parameters": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Parameter" + } + ] + } + } + }, + "examples": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Example" + } + ] + } + } + }, + "requestBodies": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/RequestBody" + } + ] + } + } + }, + "headers": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Header" + } + ] + } + } + }, + "securitySchemes": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/SecurityScheme" + } + ] + } + } + }, + "links": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Link" + } + ] + } + } + }, + "callbacks": { + "type": "object", + "patternProperties": { + "^[a-zA-Z0-9\\.\\-_]+$": { + "oneOf": [ + { + "$ref": "#/definitions/Reference" + }, + { + "$ref": "#/definitions/Callback" + } + ] + } + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Schema": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { + "type": "integer", + "minimum": 0 + }, + "minLength": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { + "type": "integer", + "minimum": 0 + }, + "minItems": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { + "type": "integer", + "minimum": 0 + }, + "minProperties": { + "type": "integer", + "minimum": 0, + "default": 0 + }, + "required": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1, + "uniqueItems": true + }, + "enum": { + "type": "array", + "items": { + }, + "minItems": 1, + "uniqueItems": false + }, + "type": { + "type": "string", + "enum": [ + "array", + "boolean", + "integer", + "number", + "object", + "string" + ] + }, + "not": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "allOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "oneOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "anyOf": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "properties": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + }, + { + "type": "boolean" + } + ], + "default": true + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "default": { + }, + "nullable": { + "type": "boolean", + "default": false + }, + "discriminator": { + "$ref": "#/definitions/Discriminator" + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "example": { + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "xml": { + "$ref": "#/definitions/XML" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Discriminator": { + "type": "object", + "required": [ + "propertyName" + ], + "properties": { + "propertyName": { + "type": "string" + }, + "mapping": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + }, + "XML": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "namespace": { + "type": "string", + "format": "uri" + }, + "prefix": { + "type": "string" + }, + "attribute": { + "type": "boolean", + "default": false + }, + "wrapped": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Response": { + "type": "object", + "required": [ + "description" + ], + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Link" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "MediaType": { + "type": "object", + "properties": { + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Encoding" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + } + ] + }, + "Example": { + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": { + }, + "externalValue": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Header": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string", + "enum": [ + "simple" + ], + "default": "simple" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + } + ] + }, + "Paths": { + "type": "object", + "patternProperties": { + "^\\/": { + "$ref": "#/definitions/PathItem" + }, + "^x-": { + } + }, + "additionalProperties": false + }, + "PathItem": { + "type": "object", + "properties": { + "$ref": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + } + }, + "patternProperties": { + "^(get|put|post|delete|options|head|patch|trace)$": { + "$ref": "#/definitions/Operation" + }, + "^x-": { + } + }, + "additionalProperties": false + }, + "Operation": { + "type": "object", + "required": [ + "responses" + ], + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/Parameter" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "uniqueItems": true + }, + "requestBody": { + "oneOf": [ + { + "$ref": "#/definitions/RequestBody" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "responses": { + "$ref": "#/definitions/Responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Callback" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "security": { + "type": "array", + "items": { + "$ref": "#/definitions/SecurityRequirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/definitions/Server" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Responses": { + "type": "object", + "properties": { + "default": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "patternProperties": { + "^[1-5](?:\\d{2}|XX)$": { + "oneOf": [ + { + "$ref": "#/definitions/Response" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "^x-": { + } + }, + "minProperties": 1, + "additionalProperties": false + }, + "SecurityRequirement": { + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "Tag": { + "type": "object", + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/definitions/ExternalDocumentation" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ExternalDocumentation": { + "type": "object", + "required": [ + "url" + ], + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri-reference" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ExampleXORExamples": { + "description": "Example and examples are mutually exclusive", + "not": { + "required": [ + "example", + "examples" + ] + } + }, + "SchemaXORContent": { + "description": "Schema and content are mutually exclusive, at least one is required", + "not": { + "required": [ + "schema", + "content" + ] + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ], + "description": "Some properties are not allowed if content is present", + "allOf": [ + { + "not": { + "required": [ + "style" + ] + } + }, + { + "not": { + "required": [ + "explode" + ] + } + }, + { + "not": { + "required": [ + "allowReserved" + ] + } + }, + { + "not": { + "required": [ + "example" + ] + } + }, + { + "not": { + "required": [ + "examples" + ] + } + } + ] + } + ] + }, + "Parameter": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "deprecated": { + "type": "boolean", + "default": false + }, + "allowEmptyValue": { + "type": "boolean", + "default": false + }, + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + }, + "schema": { + "oneOf": [ + { + "$ref": "#/definitions/Schema" + }, + { + "$ref": "#/definitions/Reference" + } + ] + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + }, + "minProperties": 1, + "maxProperties": 1 + }, + "example": { + }, + "examples": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Example" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "required": [ + "name", + "in" + ], + "allOf": [ + { + "$ref": "#/definitions/ExampleXORExamples" + }, + { + "$ref": "#/definitions/SchemaXORContent" + }, + { + "$ref": "#/definitions/ParameterLocation" + } + ] + }, + "ParameterLocation": { + "description": "Parameter location", + "oneOf": [ + { + "description": "Parameter in path", + "required": [ + "required" + ], + "properties": { + "in": { + "enum": [ + "path" + ] + }, + "style": { + "enum": [ + "matrix", + "label", + "simple" + ], + "default": "simple" + }, + "required": { + "enum": [ + true + ] + } + } + }, + { + "description": "Parameter in query", + "properties": { + "in": { + "enum": [ + "query" + ] + }, + "style": { + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ], + "default": "form" + } + } + }, + { + "description": "Parameter in header", + "properties": { + "in": { + "enum": [ + "header" + ] + }, + "style": { + "enum": [ + "simple" + ], + "default": "simple" + } + } + }, + { + "description": "Parameter in cookie", + "properties": { + "in": { + "enum": [ + "cookie" + ] + }, + "style": { + "enum": [ + "form" + ], + "default": "form" + } + } + } + ] + }, + "RequestBody": { + "type": "object", + "required": [ + "content" + ], + "properties": { + "description": { + "type": "string" + }, + "content": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/MediaType" + } + }, + "required": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "SecurityScheme": { + "oneOf": [ + { + "$ref": "#/definitions/APIKeySecurityScheme" + }, + { + "$ref": "#/definitions/HTTPSecurityScheme" + }, + { + "$ref": "#/definitions/OAuth2SecurityScheme" + }, + { + "$ref": "#/definitions/OpenIdConnectSecurityScheme" + } + ] + }, + "APIKeySecurityScheme": { + "type": "object", + "required": [ + "type", + "name", + "in" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "apiKey" + ] + }, + "name": { + "type": "string" + }, + "in": { + "type": "string", + "enum": [ + "header", + "query", + "cookie" + ] + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "HTTPSecurityScheme": { + "type": "object", + "required": [ + "scheme", + "type" + ], + "properties": { + "scheme": { + "type": "string" + }, + "bearerFormat": { + "type": "string" + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "http" + ] + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "oneOf": [ + { + "description": "Bearer", + "properties": { + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + }, + { + "description": "Non Bearer", + "not": { + "required": [ + "bearerFormat" + ] + }, + "properties": { + "scheme": { + "not": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + } + } + } + ] + }, + "OAuth2SecurityScheme": { + "type": "object", + "required": [ + "type", + "flows" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "oauth2" + ] + }, + "flows": { + "$ref": "#/definitions/OAuthFlows" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "OpenIdConnectSecurityScheme": { + "type": "object", + "required": [ + "type", + "openIdConnectUrl" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "openIdConnect" + ] + }, + "openIdConnectUrl": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "OAuthFlows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/definitions/ImplicitOAuthFlow" + }, + "password": { + "$ref": "#/definitions/PasswordOAuthFlow" + }, + "clientCredentials": { + "$ref": "#/definitions/ClientCredentialsFlow" + }, + "authorizationCode": { + "$ref": "#/definitions/AuthorizationCodeOAuthFlow" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ImplicitOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "PasswordOAuthFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "ClientCredentialsFlow": { + "type": "object", + "required": [ + "tokenUrl", + "scopes" + ], + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "AuthorizationCodeOAuthFlow": { + "type": "object", + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri-reference" + }, + "tokenUrl": { + "type": "string", + "format": "uri-reference" + }, + "refreshUrl": { + "type": "string", + "format": "uri-reference" + }, + "scopes": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + }, + "Link": { + "type": "object", + "properties": { + "operationId": { + "type": "string" + }, + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "parameters": { + "type": "object", + "additionalProperties": { + } + }, + "requestBody": { + }, + "description": { + "type": "string" + }, + "server": { + "$ref": "#/definitions/Server" + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false, + "not": { + "description": "Operation Id and Operation Ref are mutually exclusive", + "required": [ + "operationId", + "operationRef" + ] + } + }, + "Callback": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/PathItem" + }, + "patternProperties": { + "^x-": { + } + } + }, + "Encoding": { + "type": "object", + "properties": { + "contentType": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "oneOf": [ + { + "$ref": "#/definitions/Header" + }, + { + "$ref": "#/definitions/Reference" + } + ] + } + }, + "style": { + "type": "string", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "type": "boolean", + "default": false + } + }, + "patternProperties": { + "^x-": { + } + }, + "additionalProperties": false + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json new file mode 100644 index 0000000000..778b2edfa8 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json @@ -0,0 +1,1417 @@ +{ + "$id": "https://spec.openapis.org/oas/3.1/schema/2022-10-07", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "The description of OpenAPI v3.1.x documents without schema validation, as defined by https://spec.openapis.org/oas/v3.1.0", + "type": "object", + "properties": { + "openapi": { + "type": "string", + "pattern": "^3\\.1\\.\\d+(-.+)?$" + }, + "info": { + "$ref": "#/$defs/info" + }, + "jsonSchemaDialect": { + "type": "string", + "format": "uri", + "default": "https://spec.openapis.org/oas/3.1/dialect/base" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + }, + "default": [ + { + "url": "/" + } + ] + }, + "paths": { + "$ref": "#/$defs/paths" + }, + "webhooks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "components": { + "$ref": "#/$defs/components" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/$defs/tag" + } + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "openapi", + "info" + ], + "anyOf": [ + { + "required": [ + "paths" + ] + }, + { + "required": [ + "components" + ] + }, + { + "required": [ + "webhooks" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "info": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#info-object", + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "termsOfService": { + "type": "string", + "format": "uri" + }, + "contact": { + "$ref": "#/$defs/contact" + }, + "license": { + "$ref": "#/$defs/license" + }, + "version": { + "type": "string" + } + }, + "required": [ + "title", + "version" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "contact": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#contact-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + }, + "email": { + "type": "string", + "format": "email" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "license": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#license-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "identifier": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "name" + ], + "dependentSchemas": { + "identifier": { + "not": { + "required": [ + "url" + ] + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-object", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri-reference" + }, + "description": { + "type": "string" + }, + "variables": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/server-variable" + } + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "server-variable": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#server-variable-object", + "type": "object", + "properties": { + "enum": { + "type": "array", + "items": { + "type": "string" + }, + "minItems": 1 + }, + "default": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "default" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "components": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#components-object", + "type": "object", + "properties": { + "schemas": { + "type": "object", + "additionalProperties": { + "$dynamicRef": "#meta" + } + }, + "responses": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/response-or-reference" + } + }, + "parameters": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + }, + "requestBodies": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/request-body-or-reference" + } + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "securitySchemes": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/security-scheme-or-reference" + } + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "pathItems": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + } + }, + "patternProperties": { + "^(schemas|responses|parameters|examples|requestBodies|headers|securitySchemes|links|callbacks|pathItems)$": { + "$comment": "Enumerating all of the property names in the regex above is necessary for unevaluatedProperties to work as expected", + "propertyNames": { + "pattern": "^[a-zA-Z0-9._-]+$" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "paths": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#paths-object", + "type": "object", + "patternProperties": { + "^/": { + "$ref": "#/$defs/path-item" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#path-item-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "get": { + "$ref": "#/$defs/operation" + }, + "put": { + "$ref": "#/$defs/operation" + }, + "post": { + "$ref": "#/$defs/operation" + }, + "delete": { + "$ref": "#/$defs/operation" + }, + "options": { + "$ref": "#/$defs/operation" + }, + "head": { + "$ref": "#/$defs/operation" + }, + "patch": { + "$ref": "#/$defs/operation" + }, + "trace": { + "$ref": "#/$defs/operation" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "path-item-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/path-item" + } + }, + "operation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#operation-object", + "type": "object", + "properties": { + "tags": { + "type": "array", + "items": { + "type": "string" + } + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "type": "array", + "items": { + "$ref": "#/$defs/parameter-or-reference" + } + }, + "requestBody": { + "$ref": "#/$defs/request-body-or-reference" + }, + "responses": { + "$ref": "#/$defs/responses" + }, + "callbacks": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/callbacks-or-reference" + } + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "security": { + "type": "array", + "items": { + "$ref": "#/$defs/security-requirement" + } + }, + "servers": { + "type": "array", + "items": { + "$ref": "#/$defs/server" + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "external-documentation": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#external-documentation-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "url" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#parameter-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "path", + "cookie" + ] + }, + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "required": [ + "name", + "in" + ], + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "allowEmptyValue": { + "default": false, + "type": "boolean" + } + } + }, + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "type": "string" + }, + "explode": { + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/examples" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-path" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-header" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-query" + }, + { + "$ref": "#/$defs/parameter/dependentSchemas/schema/$defs/styles-for-cookie" + }, + { + "$ref": "#/$defs/styles-for-form" + } + ], + "$defs": { + "styles-for-path": { + "if": { + "properties": { + "in": { + "const": "path" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "enum": [ + "matrix", + "label", + "simple" + ] + }, + "required": { + "const": true + } + }, + "required": [ + "required" + ] + } + }, + "styles-for-header": { + "if": { + "properties": { + "in": { + "const": "header" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + } + } + } + }, + "styles-for-query": { + "if": { + "properties": { + "in": { + "const": "query" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + } + } + }, + "styles-for-cookie": { + "if": { + "properties": { + "in": { + "const": "cookie" + } + }, + "required": [ + "in" + ] + }, + "then": { + "properties": { + "style": { + "default": "form", + "const": "form" + } + } + } + } + } + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "parameter-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/parameter" + } + }, + "request-body": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#request-body-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "content": { + "$ref": "#/$defs/content" + }, + "required": { + "default": false, + "type": "boolean" + } + }, + "required": [ + "content" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "request-body-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/request-body" + } + }, + "content": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#fixed-fields-10", + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/media-type" + }, + "propertyNames": { + "format": "media-range" + } + }, + "media-type": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#media-type-object", + "type": "object", + "properties": { + "schema": { + "$dynamicRef": "#meta" + }, + "encoding": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/encoding" + } + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/examples" + } + ], + "unevaluatedProperties": false + }, + "encoding": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#encoding-object", + "type": "object", + "properties": { + "contentType": { + "type": "string", + "format": "media-range" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "style": { + "default": "form", + "enum": [ + "form", + "spaceDelimited", + "pipeDelimited", + "deepObject" + ] + }, + "explode": { + "type": "boolean" + }, + "allowReserved": { + "default": false, + "type": "boolean" + } + }, + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/styles-for-form" + } + ], + "unevaluatedProperties": false + }, + "responses": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#responses-object", + "type": "object", + "properties": { + "default": { + "$ref": "#/$defs/response-or-reference" + } + }, + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": { + "$ref": "#/$defs/response-or-reference" + } + }, + "minProperties": 1, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "if": { + "$comment": "either default, or at least one response code property must exist", + "patternProperties": { + "^[1-5](?:[0-9]{2}|XX)$": false + } + }, + "then" : { + "required": [ "default" ] + } + }, + "response": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#response-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "headers": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/header-or-reference" + } + }, + "content": { + "$ref": "#/$defs/content" + }, + "links": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/link-or-reference" + } + } + }, + "required": [ + "description" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "response-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/response" + } + }, + "callbacks": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#callback-object", + "type": "object", + "$ref": "#/$defs/specification-extensions", + "additionalProperties": { + "$ref": "#/$defs/path-item-or-reference" + } + }, + "callbacks-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/callbacks" + } + }, + "example": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#example-object", + "type": "object", + "properties": { + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "value": true, + "externalValue": { + "type": "string", + "format": "uri" + } + }, + "not": { + "required": [ + "value", + "externalValue" + ] + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "example-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/example" + } + }, + "link": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#link-object", + "type": "object", + "properties": { + "operationRef": { + "type": "string", + "format": "uri-reference" + }, + "operationId": { + "type": "string" + }, + "parameters": { + "$ref": "#/$defs/map-of-strings" + }, + "requestBody": true, + "description": { + "type": "string" + }, + "body": { + "$ref": "#/$defs/server" + } + }, + "oneOf": [ + { + "required": [ + "operationRef" + ] + }, + { + "required": [ + "operationId" + ] + } + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "link-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/link" + } + }, + "header": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#header-object", + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "required": { + "default": false, + "type": "boolean" + }, + "deprecated": { + "default": false, + "type": "boolean" + }, + "schema": { + "$dynamicRef": "#meta" + }, + "content": { + "$ref": "#/$defs/content", + "minProperties": 1, + "maxProperties": 1 + } + }, + "oneOf": [ + { + "required": [ + "schema" + ] + }, + { + "required": [ + "content" + ] + } + ], + "dependentSchemas": { + "schema": { + "properties": { + "style": { + "default": "simple", + "const": "simple" + }, + "explode": { + "default": false, + "type": "boolean" + } + }, + "$ref": "#/$defs/examples" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "header-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/header" + } + }, + "tag": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#tag-object", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "externalDocs": { + "$ref": "#/$defs/external-documentation" + } + }, + "required": [ + "name" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "reference": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#reference-object", + "type": "object", + "properties": { + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + } + } + }, + "schema": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#schema-object", + "$dynamicAnchor": "meta", + "type": [ + "object", + "boolean" + ] + }, + "security-scheme": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-scheme-object", + "type": "object", + "properties": { + "type": { + "enum": [ + "apiKey", + "http", + "mutualTLS", + "oauth2", + "openIdConnect" + ] + }, + "description": { + "type": "string" + } + }, + "required": [ + "type" + ], + "allOf": [ + { + "$ref": "#/$defs/specification-extensions" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-apikey" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-http-bearer" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oauth2" + }, + { + "$ref": "#/$defs/security-scheme/$defs/type-oidc" + } + ], + "unevaluatedProperties": false, + "$defs": { + "type-apikey": { + "if": { + "properties": { + "type": { + "const": "apiKey" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "name": { + "type": "string" + }, + "in": { + "enum": [ + "query", + "header", + "cookie" + ] + } + }, + "required": [ + "name", + "in" + ] + } + }, + "type-http": { + "if": { + "properties": { + "type": { + "const": "http" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "scheme": { + "type": "string" + } + }, + "required": [ + "scheme" + ] + } + }, + "type-http-bearer": { + "if": { + "properties": { + "type": { + "const": "http" + }, + "scheme": { + "type": "string", + "pattern": "^[Bb][Ee][Aa][Rr][Ee][Rr]$" + } + }, + "required": [ + "type", + "scheme" + ] + }, + "then": { + "properties": { + "bearerFormat": { + "type": "string" + } + } + } + }, + "type-oauth2": { + "if": { + "properties": { + "type": { + "const": "oauth2" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "flows": { + "$ref": "#/$defs/oauth-flows" + } + }, + "required": [ + "flows" + ] + } + }, + "type-oidc": { + "if": { + "properties": { + "type": { + "const": "openIdConnect" + } + }, + "required": [ + "type" + ] + }, + "then": { + "properties": { + "openIdConnectUrl": { + "type": "string", + "format": "uri" + } + }, + "required": [ + "openIdConnectUrl" + ] + } + } + } + }, + "security-scheme-or-reference": { + "if": { + "type": "object", + "required": [ + "$ref" + ] + }, + "then": { + "$ref": "#/$defs/reference" + }, + "else": { + "$ref": "#/$defs/security-scheme" + } + }, + "oauth-flows": { + "type": "object", + "properties": { + "implicit": { + "$ref": "#/$defs/oauth-flows/$defs/implicit" + }, + "password": { + "$ref": "#/$defs/oauth-flows/$defs/password" + }, + "clientCredentials": { + "$ref": "#/$defs/oauth-flows/$defs/client-credentials" + }, + "authorizationCode": { + "$ref": "#/$defs/oauth-flows/$defs/authorization-code" + } + }, + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false, + "$defs": { + "implicit": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "password": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "client-credentials": { + "type": "object", + "properties": { + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + }, + "authorization-code": { + "type": "object", + "properties": { + "authorizationUrl": { + "type": "string", + "format": "uri" + }, + "tokenUrl": { + "type": "string", + "format": "uri" + }, + "refreshUrl": { + "type": "string", + "format": "uri" + }, + "scopes": { + "$ref": "#/$defs/map-of-strings" + } + }, + "required": [ + "authorizationUrl", + "tokenUrl", + "scopes" + ], + "$ref": "#/$defs/specification-extensions", + "unevaluatedProperties": false + } + } + }, + "security-requirement": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#security-requirement-object", + "type": "object", + "additionalProperties": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "specification-extensions": { + "$comment": "https://spec.openapis.org/oas/v3.1.0#specification-extensions", + "patternProperties": { + "^x-": true + } + }, + "examples": { + "properties": { + "example": true, + "examples": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/example-or-reference" + } + } + } + }, + "map-of-strings": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "styles-for-form": { + "if": { + "properties": { + "style": { + "const": "form" + } + }, + "required": [ + "style" + ] + }, + "then": { + "properties": { + "explode": { + "default": true + } + } + }, + "else": { + "properties": { + "explode": { + "default": false + } + } + } + } + } +} diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json new file mode 100644 index 0000000000..4ab6505428 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json @@ -0,0 +1,128 @@ +[ + { + "op": "add", + "path": "/$defs/binding/properties/type/enum/-", + "value": "openapi" + }, + { + "op": "add", + "path": "/$defs/binding/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "openapi" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "openapi" + }, + "kind": + { + "enum": [ "client", "server" ] + }, + "options": + { + "properties": + { + "tcp": "#/$defs/binding/tcp/options", + "tls": "#/$defs/binding/tls/options", + "http": + { + "title": "Http", + "type": "object", + "properties": + { + "authorization": "$defs/options/binding/http/authorization" + }, + "additionalProperties": false + }, + "specs": + { + "title": "Specs", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "type": "string" + } + }, + "maxProperties": 1 + } + }, + "additionalProperties": false + }, + "routes": false + }, + "oneOf": + [ + { + "properties": + { + "kind": + { + "const": "server" + } + }, + "anyOf": + [ + { + "required": + [ + "exit" + ] + }, + { + "properties": + { + "routes": + { + "required": + [ + "exit" + ] + } + }, + "required": + [ + "routes" + ] + } + ] + }, + { + "properties": + { + "kind": + { + "const": "client" + }, + "routes": + { + "items": + { + "properties": + { + "exit": false + } + } + }, + "exit": false + } + } + ] + } + } + } +] diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt new file mode 100644 index 0000000000..8e97310066 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/client.rpt @@ -0,0 +1,43 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} +connected + +write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()} + +read "{\"code\": 0,\"message\": \"string\"}" diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt new file mode 100644 index 0000000000..d7a53f172b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/create.pet/server.rpt @@ -0,0 +1,47 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} + +connected + +read "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()} + +write "{\"code\": 0,\"message\": \"string\"}" +write flush diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt new file mode 100644 index 0000000000..ce2f2bb07f --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/client.rpt @@ -0,0 +1,31 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/composite0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + option zilla:ephemeral "test:composite0/http" + +write zilla:begin.ext ${http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()} +connect aborted diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt new file mode 100644 index 0000000000..c741d4e588 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/http/reject.non.composite.origin/server.rpt @@ -0,0 +1,23 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property serverAddress "zilla://streams/composite0" + +accept ${serverAddress} + option zilla:window 12 + option zilla:transmission "half-duplex" + +rejected diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt new file mode 100644 index 0000000000..11cb08ae8b --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/client.rpt @@ -0,0 +1,50 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/openapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} +connected + +write "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +write close + +read zilla:begin.ext ${openapi:matchBeginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()) + .build()} + +read "{\"code\": 0,\"message\": \"string\"}" diff --git a/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt new file mode 100644 index 0000000000..2ad7614964 --- /dev/null +++ b/incubator/binding-openapi.spec/src/main/scripts/io/aklivity/zilla/specs/binding/openapi/streams/openapi/create.pet/server.rpt @@ -0,0 +1,54 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +accept "zilla://streams/openapi0" + option zilla:window 8192 + option zilla:transmission "half-duplex" +accepted + +read zilla:begin.ext ${openapi:matchBeginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":method", "POST") + .header(":scheme", "http") + .header(":path", "/pets") + .header(":authority", "localhost:8080") + .header("content-type", "application/json") + .header("content-length", "39") + .build()) + .build()} + +connected + +read "{\"id\": 1, \"name\": \"rocky \"tag\": \"test\"}" +read closed + +write zilla:begin.ext ${openapi:beginEx() + .typeId(zilla:id("openapi")) + .operationId("createPets") + .extension(http:beginEx() + .typeId(zilla:id("http")) + .header(":status", "200") + .header("content-type", "application/json") + .header("content-length", "34") + .build()) + .build()} +write flush + +write "{\"code\": 0,\"message\": \"string\"}" +write flush diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java new file mode 100644 index 0000000000..4fdfa3e4c4 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/config/SchemaTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.JsonObject; + +import org.junit.Rule; +import org.junit.Test; + +import io.aklivity.zilla.specs.engine.config.ConfigSchemaRule; + +public class SchemaTest +{ + @Rule + public final ConfigSchemaRule schema = new ConfigSchemaRule() + .schemaPatch("io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json") + .schemaPatch("io/aklivity/zilla/specs/engine/schema/vault/test.schema.patch.json") + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config"); + + @Test + public void shouldValidateServer() + { + JsonObject config = schema.validate("server.yaml"); + + assertThat(config, not(nullValue())); + } + + @Test + public void shouldValidateClient() + { + JsonObject config = schema.validate("client.yaml"); + + assertThat(config, not(nullValue())); + } +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java new file mode 100644 index 0000000000..8624a3c0ee --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/internal/OpenapiFunctionsTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.internal; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertSame; + +import java.lang.reflect.Method; +import java.nio.ByteBuffer; + +import javax.el.ELContext; +import javax.el.FunctionMapper; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; +import org.kaazing.k3po.lang.el.BytesMatcher; +import org.kaazing.k3po.lang.internal.el.ExpressionContext; + +import io.aklivity.zilla.specs.binding.openapi.OpenapiFunctions; +import io.aklivity.zilla.specs.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.specs.binding.openapi.internal.types.stream.OpenapiBeginExFW; + +public class OpenapiFunctionsTest +{ + @Test + public void shouldResolveFunction() throws Exception + { + final ELContext ctx = new ExpressionContext(); + final FunctionMapper mapper = ctx.getFunctionMapper(); + final Method function = mapper.resolveFunction("openapi", "beginEx"); + + assertNotNull(function); + assertSame(OpenapiFunctions.class, function.getDeclaringClass()); + } + + @Test + public void shouldGenerateBeginExtension() + { + byte[] build = OpenapiFunctions.beginEx() + .typeId(0x01) + .apiId(1L) + .operationId("test") + .extension("extension".getBytes()) + .build(); + + DirectBuffer buffer = new UnsafeBuffer(build); + + OpenapiBeginExFW beginEx = new OpenapiBeginExFW().wrap(buffer, 0, buffer.capacity()); + assertEquals(1L, beginEx.apiId()); + assertEquals("test", beginEx.operationId().asString()); + assertEquals("extension".length(), beginEx.extension().sizeof()); + } + + @Test + public void shouldMatchOpenapiBeginExtension() throws Exception + { + BytesMatcher matcher = OpenapiFunctions.matchBeginEx() + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new byte[] {1}) + .build(); + + ByteBuffer byteBuf = ByteBuffer.allocate(26); + MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[1]); + + new OpenapiBeginExFW.Builder().wrap(new UnsafeBuffer(byteBuf), 0, byteBuf.capacity()) + .typeId(0x00) + .apiId(1) + .operationId("operationId") + .extension(new OctetsFW.Builder().wrap(writeBuffer, 0, 1).set(new byte[] {1}).build()) + .build(); + + assertNotNull(matcher.match(byteBuf)); + } +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java new file mode 100644 index 0000000000..2595d2c2ca --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/HttpIT.java @@ -0,0 +1,59 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class HttpIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${http}/create.pet/client", + "${http}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${http}/reject.non.composite.origin/client", + "${http}/reject.non.composite.origin/server" + }) + public void shouldRejectNonCompositeOrigin() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java new file mode 100644 index 0000000000..66b019bc53 --- /dev/null +++ b/incubator/binding-openapi.spec/src/test/java/io/aklivity/zilla/specs/binding/openapi/streams/OpenapiIT.java @@ -0,0 +1,49 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.specs.binding.openapi.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +public class OpenapiIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + @Rule + public final TestRule chain = outerRule(k3po).around(timeout); + + @Test + @Specification({ + "${openapi}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi/COPYRIGHT b/incubator/binding-openapi/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/binding-openapi/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/binding-openapi/LICENSE b/incubator/binding-openapi/LICENSE new file mode 100644 index 0000000000..f6abb6327b --- /dev/null +++ b/incubator/binding-openapi/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/binding-openapi/NOTICE b/incubator/binding-openapi/NOTICE new file mode 100644 index 0000000000..81c0ad0dcf --- /dev/null +++ b/incubator/binding-openapi/NOTICE @@ -0,0 +1,22 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::incubator::catalog-inline under Aklivity Community License Agreement + zilla::incubator::model-core under Aklivity Community License Agreement + zilla::incubator::model-json under Aklivity Community License Agreement + zilla::runtime::binding-http under The Apache Software License, Version 2.0 + zilla::runtime::binding-tcp under The Apache Software License, Version 2.0 + zilla::runtime::binding-tls under The Apache Software License, Version 2.0 + diff --git a/incubator/binding-openapi/NOTICE.template b/incubator/binding-openapi/NOTICE.template new file mode 100644 index 0000000000..67b08ca131 --- /dev/null +++ b/incubator/binding-openapi/NOTICE.template @@ -0,0 +1,16 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# + +This project also includes code under copyright of the following entities: + https://github.com/reaktivity/ diff --git a/incubator/binding-openapi/mvnw b/incubator/binding-openapi/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/binding-openapi/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/binding-openapi/mvnw.cmd b/incubator/binding-openapi/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/binding-openapi/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/binding-openapi/pom.xml b/incubator/binding-openapi/pom.xml new file mode 100644 index 0000000000..b517b8d405 --- /dev/null +++ b/incubator/binding-openapi/pom.xml @@ -0,0 +1,293 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + binding-openapi + zilla::incubator::binding-openapi + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.60 + 4 + + + + + ${project.groupId} + binding-openapi.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + io.aklivity.zilla + binding-http + ${project.version} + + + io.aklivity.zilla + binding-tcp + ${project.version} + + + io.aklivity.zilla + binding-tls + ${project.version} + + + io.aklivity.zilla + catalog-inline + ${project.version} + + + io.aklivity.zilla + model-core + ${project.version} + + + io.aklivity.zilla + model-json + ${project.version} + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http openapi + io.aklivity.zilla.runtime.binding.openapi.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + binding-openapi.spec + + + ^\Qio/aklivity/zilla/specs/binding/openapi/\E + io/aklivity/zilla/runtime/binding/openapi/internal/ + + + + + io/aklivity/zilla/specs/binding/openapi/schema/openapi.schema.patch.json, + io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.0.schema.json, + io/aklivity/zilla/specs/binding/openapi/schema/openapi.3.1.schema.json + + ${project.build.directory}/classes + + + + unpack-openapi + generate-sources + + unpack + + + + + ${project.groupId} + binding-openapi.spec + ${project.version} + ${basedir}/target/test-classes + **\/*.yaml + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/binding/openapi/internal/types/**/*.class + io/aklivity/zilla/runtime/binding/openapi/internal/model/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java new file mode 100644 index 0000000000..7b9148d492 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiConfig.java @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; + +public class OpenapiConfig +{ + public final String apiLabel; + public final long apiId; + public final String location; + public final Openapi openapi; + + public OpenapiConfig( + String apiLabel, + long apiId, + String location, + Openapi openapi) + { + this.apiLabel = apiLabel; + this.apiId = apiId; + this.location = location; + this.openapi = openapi; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java new file mode 100644 index 0000000000..1846572096 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiOptionsConfig.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenapiOptionsConfig extends OptionsConfig +{ + public final TcpOptionsConfig tcp; + public final TlsOptionsConfig tls; + public final HttpOptionsConfig http; + public final List openapis; + + public static OpenpaiOptionsConfigBuilder builder() + { + return new OpenpaiOptionsConfigBuilder<>(OpenapiOptionsConfig.class::cast); + } + + public static OpenpaiOptionsConfigBuilder builder( + Function mapper) + { + return new OpenpaiOptionsConfigBuilder<>(mapper); + } + + public OpenapiOptionsConfig( + TcpOptionsConfig tcp, + TlsOptionsConfig tls, + HttpOptionsConfig http, + List openapis) + { + this.tcp = tcp; + this.tls = tls; + this.http = http; + this.openapis = openapis; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java new file mode 100644 index 0000000000..1adda6259d --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenapiParser.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import static java.util.Collections.unmodifiableMap; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.io.InputStream; +import java.io.StringReader; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; +import org.leadpony.justify.api.JsonSchema; +import org.leadpony.justify.api.JsonValidationService; +import org.leadpony.justify.api.ProblemHandler; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.engine.config.ConfigException; + +public class OpenapiParser +{ + private final Map schemas; + + public OpenapiParser() + { + Map schemas = new Object2ObjectHashMap<>(); + schemas.put("3.0.0", schema("3.0.0")); + schemas.put("3.1.0", schema("3.1.0")); + this.schemas = unmodifiableMap(schemas); + } + + public Openapi parse( + String openapiText) + { + Openapi openApi = null; + + List errors = new LinkedList<>(); + + try + { + String openApiVersion = detectOpenApiVersion(openapiText); + + JsonValidationService service = JsonValidationService.newInstance(); + ProblemHandler handler = service.createProblemPrinter(msg -> errors.add(new ConfigException(msg))); + JsonSchema schema = schemas.get(openApiVersion); + + service.createReader(new StringReader(openapiText), schema, handler).read(); + + Jsonb jsonb = JsonbBuilder.create(); + + openApi = jsonb.fromJson(openapiText, Openapi.class); + } + catch (Exception ex) + { + errors.add(ex); + } + + if (!errors.isEmpty()) + { + Exception ex = errors.remove(0); + errors.forEach(ex::addSuppressed); + rethrowUnchecked(ex); + } + + return openApi; + } + + private JsonSchema schema( + String version) + { + InputStream schemaInput = null; + boolean detect = true; + + if (version.startsWith("3.0")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.0.schema.json"); + } + else if (version.startsWith("3.1")) + { + schemaInput = OpenapiBinding.class.getResourceAsStream("schema/openapi.3.1.schema.json"); + detect = false; + } + + JsonValidationService service = JsonValidationService.newInstance(); + + return service.createSchemaReaderFactoryBuilder() + .withSpecVersionDetection(detect) + .build() + .createSchemaReader(schemaInput) + .read(); + } + + private String detectOpenApiVersion( + String openapiText) + { + try (JsonReader reader = Json.createReader(new StringReader(openapiText))) + { + JsonObject json = reader.readObject(); + if (json.containsKey("openapi")) + { + return json.getString("openapi"); + } + else + { + throw new IllegalArgumentException("Unable to determine OpenAPI version."); + } + } + catch (Exception e) + { + throw new RuntimeException("Error reading OpenAPI document.", e); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java new file mode 100644 index 0000000000..b3ce2449f6 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/config/OpenpaiOptionsConfigBuilder.java @@ -0,0 +1,86 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class OpenpaiOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private TcpOptionsConfig tcp; + private TlsOptionsConfig tls; + private HttpOptionsConfig http; + private List openapis; + + OpenpaiOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public OpenpaiOptionsConfigBuilder tcp( + TcpOptionsConfig tcp) + { + this.tcp = tcp; + return this; + } + + public OpenpaiOptionsConfigBuilder tls( + TlsOptionsConfig tls) + { + this.tls = tls; + return this; + } + + public OpenpaiOptionsConfigBuilder http( + HttpOptionsConfig http) + { + this.http = http; + return this; + } + + public OpenpaiOptionsConfigBuilder openapi( + OpenapiConfig openapi) + { + if (openapis == null) + { + openapis = new ArrayList<>(); + } + openapis.add(openapi); + return this; + } + + @Override + public T build() + { + return mapper.apply(new OpenapiOptionsConfig(tcp, tls, http, openapis)); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java new file mode 100644 index 0000000000..8c9a3f9bd1 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBinding.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiBinding implements Binding +{ + public static final String NAME = "openapi"; + + private final OpenapiConfiguration config; + + OpenapiBinding( + OpenapiConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/openapi.schema.patch.json"); + } + + @Override + public String originType( + KindConfig kind) + { + return kind == KindConfig.CLIENT ? NAME : null; + } + + @Override + public String routedType( + KindConfig kind) + { + return kind == KindConfig.SERVER ? NAME : null; + } + + @Override + public OpenapiBindingContext supply( + EngineContext context) + { + return new OpenapiBindingContext(config, context); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java new file mode 100644 index 0000000000..048c400107 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingContext.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; + +import java.util.EnumMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiClientFactory; +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiServerFactory; +import io.aklivity.zilla.runtime.binding.openapi.internal.streams.OpenapiStreamFactory; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +final class OpenapiBindingContext implements BindingContext +{ + private final Map factories; + + OpenapiBindingContext( + OpenapiConfiguration config, + EngineContext context) + { + Map factories = new EnumMap<>(KindConfig.class); + factories.put(SERVER, new OpenapiServerFactory(config, context)); + factories.put(CLIENT, new OpenapiClientFactory(config, context)); + this.factories = factories; + } + + @Override + public BindingHandler attach( + BindingConfig binding) + { + OpenapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.attach(binding); + } + + return factory; + } + + @Override + public void detach( + BindingConfig binding) + { + OpenapiStreamFactory factory = factories.get(binding.kind); + + if (factory != null) + { + factory.detach(binding.id); + } + } + + @Override + public String toString() + { + return String.format("%s %s", getClass().getSimpleName(), factories); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java new file mode 100644 index 0000000000..d587d85f5a --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiBindingFactorySpi.java @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi; + +@Incubating +public final class OpenapiBindingFactorySpi implements BindingFactorySpi +{ + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public OpenapiBinding create( + Configuration config) + { + return new OpenapiBinding(new OpenapiConfiguration(config)); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java new file mode 100644 index 0000000000..d745d35d2c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfiguration.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class OpenapiConfiguration extends Configuration +{ + public static final LongPropertyDef OPENAPI_TARGET_ROUTE_ID; + private static final ConfigurationDef OPENAPI_CONFIG; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.binding.openapi"); + OPENAPI_TARGET_ROUTE_ID = config.property("target.route.id", -1L); + OPENAPI_CONFIG = config; + } + + public OpenapiConfiguration( + Configuration config) + { + super(OPENAPI_CONFIG, config); + } + + public long targetRouteId() + { + return OPENAPI_TARGET_ROUTE_ID.getAsLong(this); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java new file mode 100644 index 0000000000..2ece085095 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java @@ -0,0 +1,251 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.util.Collections.unmodifiableMap; +import static java.util.stream.Collector.of; +import static java.util.stream.Collector.Characteristics.IDENTITY_FINISH; +import static java.util.stream.Collectors.toCollection; +import static java.util.stream.Collectors.toList; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.agrona.AsciiSequenceView; +import org.agrona.DirectBuffer; +import org.agrona.collections.IntHashSet; +import org.agrona.collections.Long2LongHashMap; +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiBindingConfig +{ + public final long id; + public final String name; + public final KindConfig kind; + public final OpenapiOptionsConfig options; + public final List routes; + public final HttpHeaderHelper helper; + + private final long overrideRouteId; + private final IntHashSet httpOrigins; + private final Long2LongHashMap resolvedIds; + private final Object2ObjectHashMap paths; + private final Map> resolversByMethod; + + public OpenapiBindingConfig( + BindingConfig binding, + long overrideRouteId) + { + this.id = binding.id; + this.name = binding.name; + this.kind = binding.kind; + this.overrideRouteId = overrideRouteId; + this.options = OpenapiOptionsConfig.class.cast(binding.options); + this.paths = new Object2ObjectHashMap<>(); + options.openapis.forEach(c -> c.openapi.paths.forEach((k, v) -> + { + String regex = k.replaceAll("\\{[^/]+}", "[^/]+"); + regex = "^" + regex + "$"; + Pattern pattern = Pattern.compile(regex); + paths.put(pattern.matcher(""), v); + })); + + this.routes = binding.routes.stream().map(OpenapiRouteConfig::new).collect(toList()); + + this.resolvedIds = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http")) + .collect(of( + () -> new Long2LongHashMap(-1), + (m, r) -> m.put(options.openapis.stream().findFirst().get().apiId, r.id), + (m, r) -> m, + IDENTITY_FINISH + )); + + this.httpOrigins = binding.composites.stream() + .map(c -> c.bindings) + .flatMap(List::stream) + .filter(b -> b.type.equals("http")) + .map(b -> NamespacedId.namespaceId(b.id)) + .collect(toCollection(IntHashSet::new)); + this.helper = new HttpHeaderHelper(); + + Map> resolversByMethod = new TreeMap<>(CharSequence::compare); + resolversByMethod.put("POST", o -> o.post != null ? o.post.operationId : null); + resolversByMethod.put("PUT", o -> o.put != null ? o.put.operationId : null); + resolversByMethod.put("GET", o -> o.get != null ? o.get.operationId : null); + resolversByMethod.put("DELETE", o -> o.delete != null ? o.delete.operationId : null); + resolversByMethod.put("OPTIONS", o -> o.options != null ? o.options.operationId : null); + resolversByMethod.put("HEAD", o -> o.head != null ? o.head.operationId : null); + resolversByMethod.put("PATCH", o -> o.patch != null ? o.patch.operationId : null); + resolversByMethod.put("TRACE", o -> o.post != null ? o.trace.operationId : null); + this.resolversByMethod = unmodifiableMap(resolversByMethod); + } + + public boolean isCompositeNamespace( + int namespaceId) + { + return httpOrigins.contains(namespaceId); + } + + public long resolveResolvedId( + long apiId) + { + return overrideRouteId != -1 ? overrideRouteId : resolvedIds.get(apiId); + } + + public String resolveOperationId( + HttpBeginExFW httpBeginEx) + { + helper.visit(httpBeginEx); + + String operationId = null; + + resolve: + for (Map.Entry item : paths.entrySet()) + { + Matcher matcher = item.getKey(); + matcher.reset(helper.path); + if (matcher.find()) + { + OpenapiPathItem operations = item.getValue(); + operationId = resolveMethod(operations); + break resolve; + } + } + + return operationId; + } + + private String resolveMethod( + OpenapiPathItem operations) + { + Function resolver = resolversByMethod.get(helper.method); + return resolver != null ? resolver.apply(operations) : null; + } + + public OpenapiRouteConfig resolve( + long authorization) + { + return routes.stream() + .filter(r -> r.authorized(authorization)) + .findFirst() + .orElse(null); + } + + private static final class HttpHeaderHelper + { + private static final String8FW HEADER_NAME_METHOD = new String8FW(":method"); + private static final String8FW HEADER_NAME_PATH = new String8FW(":path"); + private static final String8FW HEADER_NAME_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_NAME_AUTHORITY = new String8FW(":authority"); + + private final Map> visitors; + { + Map> visitors = new HashMap<>(); + visitors.put(HEADER_NAME_METHOD, this::visitMethod); + visitors.put(HEADER_NAME_PATH, this::visitPath); + visitors.put(HEADER_NAME_SCHEME, this::visitScheme); + visitors.put(HEADER_NAME_AUTHORITY, this::visitAuthority); + this.visitors = visitors; + } + private final AsciiSequenceView methodRO = new AsciiSequenceView(); + private final AsciiSequenceView pathRO = new AsciiSequenceView(); + private final String16FW schemeRO = new String16FW(); + private final String16FW authorityRO = new String16FW(); + + public CharSequence path; + public CharSequence method; + public String16FW scheme; + public String16FW authority; + + private void visit( + HttpBeginExFW beginEx) + { + method = null; + path = null; + scheme = null; + authority = null; + + if (beginEx != null) + { + beginEx.headers().forEach(this::dispatch); + } + } + + private boolean dispatch( + HttpHeaderFW header) + { + final String8FW name = header.name(); + final Consumer visitor = visitors.get(name); + if (visitor != null) + { + visitor.accept(header.value()); + } + + return method != null && + path != null && + scheme != null && + authority != null; + } + + private void visitMethod( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + method = methodRO.wrap(buffer, offset, length); + } + + private void visitPath( + String16FW value) + { + final DirectBuffer buffer = value.buffer(); + final int offset = value.offset() + value.fieldSizeLength(); + final int length = value.sizeof() - value.fieldSizeLength(); + path = pathRO.wrap(buffer, offset, length); + } + + private void visitScheme( + String16FW value) + { + scheme = schemeRO.wrap(value.buffer(), value.offset(), value.limit()); + } + + private void visitAuthority( + String16FW value) + { + authority = authorityRO.wrap(value.buffer(), value.offset(), value.limit()); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java new file mode 100644 index 0000000000..c49479ef35 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientCompositeBindingAdapter.java @@ -0,0 +1,255 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static java.util.Objects.requireNonNull; + +import java.net.URI; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpResponseConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiHeader; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponse; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiOperationView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiOperationsView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiServerView; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public final class OpenapiClientCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String INLINE_CATALOG_NAME = "catalog0"; + + private final Map models = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + public OpenapiClientCompositeBindingAdapter() + { + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; + OpenapiConfig openapiConfig = options.openapis.get(0); + + final Openapi openApi = openapiConfig.openapi; + final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); + final boolean secure = httpsPorts != null; + + return BindingConfig.builder(binding) + .composite() + .name(String.format(binding.qname, "$composite")) + .binding() + .name("http_client0") + .type("http") + .kind(CLIENT) + .inject(b -> this.injectHttpClientOptions(b, openApi)) + .exit(secure ? "tls_client0" : "tcp_client0") + .build() + .inject(b -> this.injectTlsClient(b, options.tls, secure)) + .binding() + .name("tcp_client0") + .type("tcp") + .kind(CLIENT) + .options(options.tcp) + .build() + .build() + .build(); + } + + private int[] resolvePortsForScheme( + Openapi openApi, + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(openApi, scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + private URI findFirstServerUrlWithScheme( + Openapi openApi, + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (OpenapiServer item : openApi.servers) + { + OpenapiServerView server = OpenapiServerView.of(item); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + private BindingConfigBuilder injectHttpClientOptions( + BindingConfigBuilder binding, + Openapi openApi) + { + OpenapiOperationsView operations = OpenapiOperationsView.of(openApi.paths); + if (operations.hasResponses()) + { + binding. + options(HttpOptionsConfig::builder) + .inject(options -> injectHttpClientRequests(operations, options, openApi)) + .build(); + } + return binding; + } + + private HttpOptionsConfigBuilder injectHttpClientRequests( + OpenapiOperationsView operations, + HttpOptionsConfigBuilder options, + Openapi openApi) + { + for (String pathName : openApi.paths.keySet()) + { + OpenapiPathView path = OpenapiPathView.of(openApi.paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenapiOperationView operation = operations.operation(pathName, methodName); + if (operation.hasResponses()) + { + options + .request() + .path(pathName) + .method(HttpRequestConfig.Method.valueOf(methodName)) + .inject(request -> injectResponses(request, operation, openApi)) + .build() + .build(); + } + } + } + return options; + } + + private HttpRequestConfigBuilder injectResponses( + HttpRequestConfigBuilder request, + OpenapiOperationView operation, + Openapi openApi) + { + if (operation != null && operation.responsesByStatus() != null) + { + for (Map.Entry responses0 : operation.responsesByStatus().entrySet()) + { + String status = responses0.getKey(); + OpenapiResponseByContentType responses1 = responses0.getValue(); + if (!(OpenapiOperationView.DEFAULT.equals(status)) && responses1.content != null) + { + for (Map.Entry response2 : responses1.content.entrySet()) + { + OpenapiSchemaView schema = OpenapiSchemaView.of(openApi.components.schemas, response2.getValue().schema); + request + .response() + .status(Integer.parseInt(status)) + .contentType(response2.getKey()) + .inject(response -> injectResponseHeaders(responses1, response)) + .content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .schema() + .subject(schema.refKey()) + .build() + .build() + .build() + .build(); + } + } + } + } + return request; + } + + private HttpResponseConfigBuilder injectResponseHeaders( + OpenapiResponseByContentType responses, + HttpResponseConfigBuilder response) + { + if (responses.headers != null && !responses.headers.isEmpty()) + { + for (Map.Entry header : responses.headers.entrySet()) + { + String name = header.getKey(); + ModelConfig model = models.get(header.getValue().schema.type); + if (model != null) + { + response + .header() + .name(name) + .model(model) + .build(); + } + } + } + return response; + } + + private NamespaceConfigBuilder injectTlsClient( + NamespaceConfigBuilder namespace, + TlsOptionsConfig tlsConfig, + boolean secure) + { + if (secure) + { + namespace + .binding() + .name("tls_client0") + .type("tls") + .kind(CLIENT) + .options(tlsConfig) + .vault("client") + .exit("tcp_client0") + .build(); + } + return namespace; + } + +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java new file mode 100644 index 0000000000..06ad49a40c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiCompositeBindingAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; +import static java.util.function.UnaryOperator.identity; + +import java.util.EnumMap; +import java.util.Map; +import java.util.function.UnaryOperator; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.KindConfig; + +public final class OpenapiCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private final UnaryOperator composite; + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + public OpenapiCompositeBindingAdapter() + { + Map> composites = new EnumMap<>(KindConfig.class); + composites.put(SERVER, new OpenapiServerCompositeBindingAdapter()::adapt); + composites.put(CLIENT, new OpenapiClientCompositeBindingAdapter()::adapt); + UnaryOperator composite = binding -> composites + .getOrDefault(binding.kind, identity()).apply(binding); + this.composite = composite; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + return composite.apply(binding); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java new file mode 100644 index 0000000000..caf59177df --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapter.java @@ -0,0 +1,168 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.function.Function; +import java.util.zip.CRC32C; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonString; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiParser; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenpaiOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; + +public final class OpenapiOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + private static final String TCP_NAME = "tcp"; + private static final String TLS_NAME = "tls"; + private static final String HTTP_NAME = "http"; + private static final String SPECS_NAME = "specs"; + + private final OpenapiParser parser; + private final CRC32C crc; + + private OptionsConfigAdapter tcpOptions; + private OptionsConfigAdapter tlsOptions; + private OptionsConfigAdapter httpOptions; + private Function readURL; + + public OpenapiOptionsConfigAdapter() + { + this.parser = new OpenapiParser(); + this.crc = new CRC32C(); + } + + @Override + public Kind kind() + { + return Kind.BINDING; + } + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + OpenapiOptionsConfig openapiOptions = (OpenapiOptionsConfig) options; + + JsonObjectBuilder object = Json.createObjectBuilder(); + + if (openapiOptions.tcp != null) + { + final TcpOptionsConfig tcp = ((OpenapiOptionsConfig) options).tcp; + object.add(TCP_NAME, tcpOptions.adaptToJson(tcp)); + } + + if (openapiOptions.tls != null) + { + final TlsOptionsConfig tls = ((OpenapiOptionsConfig) options).tls; + object.add(TLS_NAME, tlsOptions.adaptToJson(tls)); + } + + HttpOptionsConfig http = openapiOptions.http; + if (http != null) + { + object.add(HTTP_NAME, httpOptions.adaptToJson(http)); + } + + if (openapiOptions.openapis != null) + { + JsonObjectBuilder openapi = Json.createObjectBuilder(); + openapiOptions.openapis.forEach(o -> openapi.add(o.apiLabel, o.location)); + object.add(SPECS_NAME, openapi); + } + + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + OpenpaiOptionsConfigBuilder openapiOptions = OpenapiOptionsConfig.builder(); + + if (object.containsKey(TCP_NAME)) + { + final JsonObject tcp = object.getJsonObject(TCP_NAME); + final TcpOptionsConfig tcpOptions = (TcpOptionsConfig) this.tcpOptions.adaptFromJson(tcp); + openapiOptions.tcp(tcpOptions); + } + + if (object.containsKey(TLS_NAME)) + { + final JsonObject tls = object.getJsonObject(TLS_NAME); + final TlsOptionsConfig tlsOptions = (TlsOptionsConfig) this.tlsOptions.adaptFromJson(tls); + openapiOptions.tls(tlsOptions); + } + + if (object.containsKey(HTTP_NAME)) + { + JsonObject http = object.getJsonObject(HTTP_NAME); + + final HttpOptionsConfig httpOptions = (HttpOptionsConfig) this.httpOptions.adaptFromJson(http); + openapiOptions.http(httpOptions); + } + + if (object.containsKey(SPECS_NAME)) + { + JsonObject openapi = object.getJsonObject(SPECS_NAME); + openapi.forEach((n, v) -> + { + final String location = JsonString.class.cast(v).getString(); + final String specText = readURL.apply(location); + final String apiLabel = n; + crc.reset(); + crc.update(specText.getBytes(UTF_8)); + final long apiId = crc.getValue(); + openapiOptions.openapi(new OpenapiConfig(apiLabel, apiId, location, parser.parse(specText))); + }); + } + + return openapiOptions.build(); + } + + @Override + public void adaptContext( + ConfigAdapterContext context) + { + this.readURL = context::readURL; + this.tcpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tcpOptions.adaptType("tcp"); + this.tlsOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.tlsOptions.adaptType("tls"); + this.httpOptions = new OptionsConfigAdapter(Kind.BINDING, context); + this.httpOptions.adaptType("http"); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java new file mode 100644 index 0000000000..82b092dc23 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiRouteConfig.java @@ -0,0 +1,39 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import java.util.function.LongPredicate; + +import io.aklivity.zilla.runtime.engine.config.RouteConfig; + +public final class OpenapiRouteConfig +{ + public final long id; + + private final LongPredicate authorized; + + public OpenapiRouteConfig( + RouteConfig route) + { + this.id = route.id; + this.authorized = route.authorized; + } + + boolean authorized( + long authorization) + { + return authorized.test(authorization); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java new file mode 100644 index 0000000000..b98aca9058 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerCompositeBindingAdapter.java @@ -0,0 +1,503 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN; +import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER; +import static java.util.Objects.requireNonNull; +import static org.agrona.LangUtil.rethrowUnchecked; + +import java.net.URI; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; + +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.http.config.HttpAuthorizationConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpConditionConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfig; +import io.aklivity.zilla.runtime.binding.http.config.HttpRequestConfigBuilder; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiMediaType; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiParameter; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiSchema; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiPathView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiSchemaView; +import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiServerView; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineOptionsConfig; +import io.aklivity.zilla.runtime.catalog.inline.config.InlineSchemaConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; +import io.aklivity.zilla.runtime.engine.config.GuardedConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; +import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder; +import io.aklivity.zilla.runtime.model.core.config.IntegerModelConfig; +import io.aklivity.zilla.runtime.model.core.config.StringModelConfig; +import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig; + +public final class OpenapiServerCompositeBindingAdapter implements CompositeBindingAdapterSpi +{ + private static final String INLINE_CATALOG_NAME = "catalog0"; + private static final String INLINE_CATALOG_TYPE = "inline"; + private static final String VERSION_LATEST = "latest"; + private static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$"); + + private final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher(""); + private final Map models = Map.of( + "string", StringModelConfig.builder().build(), + "integer", IntegerModelConfig.builder().build() + ); + + @Override + public String type() + { + return OpenapiBinding.NAME; + } + + @Override + public BindingConfig adapt( + BindingConfig binding) + { + OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options; + OpenapiConfig openapiConfig = options.openapis.get(0); + + final Openapi openApi = openapiConfig.openapi; + final TlsOptionsConfig tlsOption = options.tls != null ? options.tls : null; + final HttpOptionsConfig httpOptions = options.http; + final String guardName = httpOptions != null ? httpOptions.authorization.name : null; + final HttpAuthorizationConfig authorization = httpOptions != null ? httpOptions.authorization : null; + + final int[] allPorts = resolveAllPorts(openApi); + final int[] httpPorts = resolvePortsForScheme(openApi, "http"); + final int[] httpsPorts = resolvePortsForScheme(openApi, "https"); + final boolean secure = httpsPorts != null; + final Map securitySchemes = resolveSecuritySchemes(openApi); + final boolean hasJwt = !securitySchemes.isEmpty(); + + return BindingConfig.builder(binding) + .composite() + .name(String.format("%s/http", binding.qname)) + .inject(n -> this.injectCatalog(n, openApi)) + .binding() + .name("tcp_server0") + .type("tcp") + .kind(SERVER) + .options(TcpOptionsConfig::builder) + .host("0.0.0.0") + .ports(allPorts) + .build() + .inject(b -> this.injectPlainTcpRoute(b, httpPorts, secure)) + .inject(b -> this.injectTlsTcpRoute(b, httpsPorts, secure)) + .build() + .inject(n -> this.injectTlsServer(n, tlsOption, secure)) + .binding() + .name("http_server0") + .type("http") + .kind(SERVER) + .options(HttpOptionsConfig::builder) + .access() + .policy(CROSS_ORIGIN) + .build() + .inject(o -> this.injectHttpServerOptions(o, authorization, hasJwt)) + .inject(r -> this.injectHttpServerRequests(r, openApi)) + .build() + .inject(b -> this.injectHttpServerRoutes(b, openApi, binding.qname, guardName, securitySchemes)) + .build() + .build() + .build(); + } + + private BindingConfigBuilder injectPlainTcpRoute( + BindingConfigBuilder binding, + int[] httpPorts, + boolean secure) + { + if (!secure) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(httpPorts) + .build() + .exit("http_server0") + .build(); + } + return binding; + } + + private BindingConfigBuilder injectTlsTcpRoute( + BindingConfigBuilder binding, + int[] httpsPorts, + boolean secure) + { + if (secure) + { + binding + .route() + .when(TcpConditionConfig::builder) + .ports(httpsPorts) + .build() + .exit("tls_server0") + .build(); + } + return binding; + } + + private NamespaceConfigBuilder injectTlsServer( + NamespaceConfigBuilder namespace, + TlsOptionsConfig tls, + boolean secure) + { + if (secure) + { + namespace + .binding() + .name("tls_server0") + .type("tls") + .kind(SERVER) + .options(tls) + .vault("server") + .exit("http_server0") + .build(); + } + return namespace; + } + + private HttpOptionsConfigBuilder injectHttpServerOptions( + HttpOptionsConfigBuilder options, + HttpAuthorizationConfig authorization, + boolean hasJwt) + { + if (hasJwt) + { + options.authorization(authorization).build(); + } + return options; + } + + private HttpOptionsConfigBuilder injectHttpServerRequests( + HttpOptionsConfigBuilder options, + Openapi openapi) + { + for (String pathName : openapi.paths.keySet()) + { + OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + final OpenapiOperation operation = path.methods().get(methodName); + if (operation.requestBody != null || + operation.parameters != null && + !operation.parameters.isEmpty()) + { + options + .request() + .path(pathName) + .method(HttpRequestConfig.Method.valueOf(methodName)) + .inject(request -> injectContent(request, operation, openapi)) + .inject(request -> injectParams(request, operation)) + .build(); + } + } + } + return options; + } + + private HttpRequestConfigBuilder injectContent( + HttpRequestConfigBuilder request, + OpenapiOperation operation, + Openapi openApi) + { + if (operation.requestBody != null && + operation.requestBody.content != null && + !operation.requestBody.content.isEmpty()) + { + OpenapiSchemaView schema = resolveSchemaForJsonContentType(operation.requestBody.content, openApi); + if (schema != null) + { + request. + content(JsonModelConfig::builder) + .catalog() + .name(INLINE_CATALOG_NAME) + .schema() + .subject(schema.refKey()) + .version(VERSION_LATEST) + .build() + .build() + .build(); + } + } + return request; + } + + private HttpRequestConfigBuilder injectParams( + HttpRequestConfigBuilder request, + OpenapiOperation operation) + { + if (operation != null && operation.parameters != null) + { + for (OpenapiParameter parameter : operation.parameters) + { + if (parameter.schema != null && parameter.schema.type != null) + { + ModelConfig model = models.get(parameter.schema.type); + if (model != null) + { + switch (parameter.in) + { + case "path": + request. + pathParam() + .name(parameter.name) + .model(model) + .build(); + break; + case "query": + request. + queryParam() + .name(parameter.name) + .model(model) + .build(); + break; + case "header": + request. + header() + .name(parameter.name) + .model(model) + .build(); + break; + } + } + } + } + } + return request; + } + + private BindingConfigBuilder injectHttpServerRoutes( + BindingConfigBuilder binding, + Openapi openApi, + String qname, + String guardName, + Map securitySchemes) + { + for (String item : openApi.paths.keySet()) + { + OpenapiPathView path = OpenapiPathView.of(openApi.paths.get(item)); + for (String method : path.methods().keySet()) + { + binding + .route() + .exit(qname) + .when(HttpConditionConfig::builder) + .header(":path", item.replaceAll("\\{[^}]+\\}", "*")) + .header(":method", method) + .build() + .inject(route -> injectHttpServerRouteGuarded(route, path, method, guardName, securitySchemes)) + .build(); + } + } + return binding; + } + + private RouteConfigBuilder injectHttpServerRouteGuarded( + RouteConfigBuilder route, + OpenapiPathView path, + String method, + String guardName, + Map securitySchemes) + { + final List>> security = path.methods().get(method).security; + final boolean hasJwt = securitySchemes.isEmpty(); + + if (security != null) + { + for (Map> securityItem : security) + { + for (String securityItemLabel : securityItem.keySet()) + { + if (hasJwt && "jwt".equals(securitySchemes.get(securityItemLabel))) + { + route + .guarded() + .name(guardName) + .inject(guarded -> injectGuardedRoles(guarded, securityItem.get(securityItemLabel))) + .build(); + } + } + } + } + return route; + } + + private GuardedConfigBuilder injectGuardedRoles( + GuardedConfigBuilder guarded, + List roles) + { + for (String role : roles) + { + guarded.role(role); + } + return guarded; + } + + private NamespaceConfigBuilder injectCatalog( + NamespaceConfigBuilder namespace, + Openapi openApi) + { + if (openApi.components != null && + openApi.components.schemas != null && + !openApi.components.schemas.isEmpty()) + { + namespace + .catalog() + .name(INLINE_CATALOG_NAME) + .type(INLINE_CATALOG_TYPE) + .options(InlineOptionsConfig::builder) + .subjects() + .inject(s -> this.injectSubjects(s, openApi)) + .build() + .build() + .build(); + } + return namespace; + } + + private InlineSchemaConfigBuilder injectSubjects( + InlineSchemaConfigBuilder subjects, + Openapi openApi) + { + try (Jsonb jsonb = JsonbBuilder.create()) + { + for (Map.Entry entry : openApi.components.schemas.entrySet()) + { + OpenapiSchemaView schemaView = OpenapiSchemaView.of(openApi.components.schemas, entry.getValue()); + OpenapiSchema schema = schemaView.ref() != null ? schemaView.ref() : entry.getValue(); + + subjects + .subject(entry.getKey()) + .schema(jsonb.toJson(schema)) + .version(VERSION_LATEST) + .build(); + } + } + catch (Exception ex) + { + rethrowUnchecked(ex); + } + return subjects; + } + + private int[] resolveAllPorts( + Openapi openApi) + { + int[] ports = new int[openApi.servers.size()]; + for (int i = 0; i < openApi.servers.size(); i++) + { + OpenapiServerView server = OpenapiServerView.of(openApi.servers.get(i)); + URI url = server.url(); + ports[i] = url.getPort(); + } + return ports; + } + + private int[] resolvePortsForScheme( + Openapi openApi, + String scheme) + { + requireNonNull(scheme); + int[] ports = null; + URI url = findFirstServerUrlWithScheme(openApi, scheme); + if (url != null) + { + ports = new int[] {url.getPort()}; + } + return ports; + } + + private URI findFirstServerUrlWithScheme( + Openapi openApi, + String scheme) + { + requireNonNull(scheme); + URI result = null; + for (OpenapiServer item : openApi.servers) + { + OpenapiServerView server = OpenapiServerView.of(item); + if (scheme.equals(server.url().getScheme())) + { + result = server.url(); + break; + } + } + return result; + } + + private Map resolveSecuritySchemes( + Openapi openApi) + { + requireNonNull(openApi); + Map result = new Object2ObjectHashMap<>(); + if (openApi.components != null && + openApi.components.securitySchemes != null) + { + for (String securitySchemeName : openApi.components.securitySchemes.keySet()) + { + String guardType = openApi.components.securitySchemes.get(securitySchemeName).bearerFormat; + if ("jwt".equals(guardType)) + { + result.put(securitySchemeName, guardType); + } + } + } + return result; + } + + private OpenapiSchemaView resolveSchemaForJsonContentType( + Map content, + Openapi openApi) + { + OpenapiMediaType mediaType = null; + if (content != null) + { + for (String contentType : content.keySet()) + { + if (jsonContentType.reset(contentType).matches()) + { + mediaType = content.get(contentType); + break; + } + } + } + + return mediaType == null ? null : OpenapiSchemaView.of(openApi.components.schemas, mediaType.schema); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java new file mode 100644 index 0000000000..f9a144fa31 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/Openapi.java @@ -0,0 +1,26 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class Openapi +{ + public String openapi; + public List servers; + public Map paths; + public OpenapiComponents components; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiBearerAuth.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiBearerAuth.java new file mode 100644 index 0000000000..0d781d1425 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiBearerAuth.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiBearerAuth +{ + public String bearerFormat; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiComponents.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiComponents.java new file mode 100644 index 0000000000..b57f8016f4 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiComponents.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenapiComponents +{ + public Map securitySchemes; + public Map schemas; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiHeader.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiHeader.java new file mode 100644 index 0000000000..634c68d604 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiHeader.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiHeader +{ + public OpenapiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiItem.java new file mode 100644 index 0000000000..852493a3e3 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiItem.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiItem +{ + public String type; + public String description; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiMediaType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiMediaType.java new file mode 100644 index 0000000000..fb74808582 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiMediaType.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiMediaType +{ + public OpenapiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiOperation.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiOperation.java new file mode 100644 index 0000000000..a400d5c838 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiOperation.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +public class OpenapiOperation +{ + public List>> security; + public String operationId; + public OpenapiRequestBody requestBody; + public List parameters; + public Map responses; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiParameter.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiParameter.java new file mode 100644 index 0000000000..585ed26242 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiParameter.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiParameter +{ + public String name; + public String in; + public boolean required; + public OpenapiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java new file mode 100644 index 0000000000..28e3910280 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiPathItem.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiPathItem +{ + public OpenapiOperation get; + public OpenapiOperation put; + public OpenapiOperation post; + public OpenapiOperation delete; + public OpenapiOperation options; + public OpenapiOperation head; + public OpenapiOperation patch; + public OpenapiOperation trace; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiRequestBody.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiRequestBody.java new file mode 100644 index 0000000000..8e63e2eda7 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiRequestBody.java @@ -0,0 +1,22 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenapiRequestBody +{ + public Map content; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponse.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponse.java new file mode 100644 index 0000000000..35bb9bb49e --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponse.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiResponse +{ + public OpenapiSchema schema; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java new file mode 100644 index 0000000000..b75951a387 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiResponseByContentType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.Map; + +public class OpenapiResponseByContentType +{ + public Map headers; + public Map content; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSchema.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSchema.java new file mode 100644 index 0000000000..c78a8b2a9f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSchema.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbProperty; + +public class OpenapiSchema +{ + public String type; + public OpenapiSchema items; + public Map properties; + public List required; + + @JsonbProperty("$ref") + public String ref; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSecurityScheme.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSecurityScheme.java new file mode 100644 index 0000000000..d372dd908e --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiSecurityScheme.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiSecurityScheme +{ + public String bearerFormat; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiServer.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiServer.java new file mode 100644 index 0000000000..b62d40eeec --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/model/OpenapiServer.java @@ -0,0 +1,20 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.model; + +public class OpenapiServer +{ + public String url; +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java new file mode 100644 index 0000000000..658a7a5e84 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientFactory.java @@ -0,0 +1,1066 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public final class OpenapiClientFactory implements OpenapiStreamFactory +{ + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + + private final OpenapiBeginExFW.Builder openBeginExRW = new OpenapiBeginExFW.Builder(); + + private final OpenapiConfiguration config; + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int openapiTypeId; + private final int httpTypeId; + + + public OpenapiClientFactory( + OpenapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return httpTypeId; + } + + @Override + public int routedTypeId() + { + return openapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiBindingConfig openapiBinding = new OpenapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, openapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + final OpenapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null) + { + final long apiId = openapiBeginEx.apiId(); + final String operationId = openapiBeginEx.operationId().asString(); + + final long resolvedId = binding.resolveResolvedId(apiId); + + if (resolvedId != -1) + { + newStream = new OpenapiStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + resolvedId, + operationId)::onOpenapiMessage; + } + + } + + return newStream; + } + + private final class OpenapiStream + { + private final HttpStream http; + private final MessageConsumer sender; + private final String operationId; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + private long replyBud; + private int replyCap; + + private OpenapiStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + String operationId) + { + this.http = new HttpStream(this, routedId, resolvedId, authorization); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + this.operationId = operationId; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenapiEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenapiWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpBegin(traceId, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + http.doHttpData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onOpenapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpEnd(traceId, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + http.doHttpFlush(traceId, reserved, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + http.doHttpAbort(traceId, extension); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onOpenapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + final int capabilities = window.capabilities(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyBud = budgetId; + replyPad = padding; + replyCap = capabilities; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + http.doHttpWindow(traceId, acknowledge, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + OctetsFW extension) + { + state = OpenapiState.openingReply(state); + + final OpenapiBeginExFW openBeginEx = openBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .operationId(operationId) + .extension(extension) + .build(); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, openBeginEx); + } + + private void doOpenapiData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + + replySeq += reserved; + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiState.closeReply(state); + } + + private void doOpenapiAbort( + long traceId) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiState.closeInitial(state); + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiState.initialClosed(state)) + { + state = OpenapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doOpenapiWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = http.initialAck; + initialMax = http.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doOpenapiReset(traceId); + doOpenapiAbort(traceId); + + http.cleanup(traceId); + } + } + + private final class HttpStream + { + private final OpenapiStream delegate; + private final long originId; + private final long routedId; + private final long authorization; + + private final long initialId; + private final long replyId; + + private MessageConsumer receiver; + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + + private HttpStream( + OpenapiStream delegate, + long originId, + long routedId, + long authorization) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.receiver = MessageConsumer.NOOP; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.authorization = authorization; + } + + private void onHttpMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onHttpBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onHttpData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onHttpFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onHttpEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onHttpAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onHttpReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onHttpWindow(window); + break; + default: + break; + } + } + + private void onHttpBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiState.openingReply(state); + + delegate.doOpenapiBegin(traceId, extension); + } + + private void onHttpData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiData(traceId, flags, reserved, payload, extension); + } + + private void onHttpFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doOpenapiFlush(traceId, reserved, extension); + } + + private void onHttpEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiEnd(traceId, extension); + } + + private void onHttpAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doOpenapiAbort(traceId); + } + + private void onHttpReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doOpenapiReset(traceId); + } + + private void onHttpWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + delegate.doOpenapiWindow(authorization, traceId, budgetId, padding); + } + + private void doHttpBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialOpening(state)) + { + assert state == 0; + + this.receiver = newStream(this::onHttpMessage, originId, routedId, initialId, initialSeq, + initialAck, initialMax, traceId, authorization, 0L, extension); + state = OpenapiState.openingInitial(state); + } + } + + private void doHttpData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doHttpFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doHttpEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doHttpAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doHttpReset( + long traceId) + { + if (!OpenapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiState.closeReply(state); + } + } + + private void doHttpWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - padding, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doHttpAbort(traceId, EMPTY_OCTETS); + doHttpReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java new file mode 100644 index 0000000000..05c9c7891f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerFactory.java @@ -0,0 +1,1077 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import java.util.function.LongSupplier; +import java.util.function.LongUnaryOperator; + +import org.agrona.DirectBuffer; +import org.agrona.MutableDirectBuffer; +import org.agrona.collections.Long2ObjectHashMap; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBinding; +import io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiBindingConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiRouteConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.Flyweight; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.OctetsFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.AbortFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.BeginFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.DataFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.EndFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.FlushFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.HttpBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.OpenapiBeginExFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.ResetFW; +import io.aklivity.zilla.runtime.binding.openapi.internal.types.stream.WindowFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.buffer.BufferPool; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; +import io.aklivity.zilla.runtime.engine.namespace.NamespacedId; + +public final class OpenapiServerFactory implements OpenapiStreamFactory +{ + private static final String HTTP_TYPE_NAME = "http"; + private static final OctetsFW EMPTY_OCTETS = new OctetsFW().wrap(new UnsafeBuffer(), 0, 0); + + private final BeginFW beginRO = new BeginFW(); + private final DataFW dataRO = new DataFW(); + private final EndFW endRO = new EndFW(); + private final FlushFW flushRO = new FlushFW(); + private final AbortFW abortRO = new AbortFW(); + private final WindowFW windowRO = new WindowFW(); + private final ResetFW resetRO = new ResetFW(); + + private final BeginFW.Builder beginRW = new BeginFW.Builder(); + private final DataFW.Builder dataRW = new DataFW.Builder(); + private final EndFW.Builder endRW = new EndFW.Builder(); + private final AbortFW.Builder abortRW = new AbortFW.Builder(); + private final WindowFW.Builder windowRW = new WindowFW.Builder(); + private final ResetFW.Builder resetRW = new ResetFW.Builder(); + private final FlushFW.Builder flushRW = new FlushFW.Builder(); + + private final OpenapiBeginExFW openapiBeginExRO = new OpenapiBeginExFW(); + private final HttpBeginExFW httpBeginExRO = new HttpBeginExFW(); + + private final OpenapiBeginExFW.Builder openapiBeginExRW = new OpenapiBeginExFW.Builder(); + + private final OpenapiConfiguration config; + private final MutableDirectBuffer writeBuffer; + private final MutableDirectBuffer extBuffer; + private final BufferPool bufferPool; + private final BindingHandler streamFactory; + private final LongUnaryOperator supplyInitialId; + private final LongUnaryOperator supplyReplyId; + private final LongSupplier supplyTraceId; + private final Long2ObjectHashMap bindings; + private final int openapiTypeId; + private final int httpTypeId; + + public OpenapiServerFactory( + OpenapiConfiguration config, + EngineContext context) + { + this.config = config; + this.writeBuffer = context.writeBuffer(); + this.extBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]); + this.bufferPool = context.bufferPool(); + this.streamFactory = context.streamFactory(); + this.supplyInitialId = context::supplyInitialId; + this.supplyReplyId = context::supplyReplyId; + this.supplyTraceId = context::supplyTraceId; + this.bindings = new Long2ObjectHashMap<>(); + this.openapiTypeId = context.supplyTypeId(OpenapiBinding.NAME); + this.httpTypeId = context.supplyTypeId(HTTP_TYPE_NAME); + } + + @Override + public int originTypeId() + { + return httpTypeId; + } + + @Override + public int routedTypeId() + { + return openapiTypeId; + } + + @Override + public void attach( + BindingConfig binding) + { + OpenapiBindingConfig openapiBinding = new OpenapiBindingConfig(binding, config.targetRouteId()); + bindings.put(binding.id, openapiBinding); + } + + @Override + public void detach( + long bindingId) + { + bindings.remove(bindingId); + } + + @Override + public MessageConsumer newStream( + int msgTypeId, + DirectBuffer buffer, + int index, + int length, + MessageConsumer receiver) + { + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long originId = begin.originId(); + final long routedId = begin.routedId(); + final long initialId = begin.streamId(); + final long affinity = begin.affinity(); + final long authorization = begin.authorization(); + final OctetsFW extension = begin.extension(); + final HttpBeginExFW httpBeginEx = extension.get(httpBeginExRO::tryWrap); + + final OpenapiBindingConfig binding = bindings.get(routedId); + + MessageConsumer newStream = null; + + if (binding != null && binding.isCompositeNamespace(NamespacedId.namespaceId(originId))) + { + + final OpenapiRouteConfig route = binding.resolve(authorization); + + if (route != null) + { + final long apiId = binding.options.openapis.get(0).apiId; + final String operationId = binding.resolveOperationId(httpBeginEx); + + newStream = new HttpStream( + receiver, + originId, + routedId, + initialId, + affinity, + authorization, + route.id, + apiId, + operationId)::onHttpMessage; + } + + } + + return newStream; + } + + private final class HttpStream + { + private final OpenapiStream openapi; + private final MessageConsumer sender; + private final long originId; + private final long routedId; + private final long initialId; + private final long replyId; + private final long affinity; + private final long authorization; + + private int state; + + private long replyBudgetId; + + private long initialSeq; + private long initialAck; + private int initialMax; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private HttpStream( + MessageConsumer sender, + long originId, + long routedId, + long initialId, + long affinity, + long authorization, + long resolvedId, + long apiId, + String operationId) + { + this.openapi = new OpenapiStream(this, routedId, resolvedId, authorization, apiId, operationId); + this.sender = sender; + this.originId = originId; + this.routedId = routedId; + this.initialId = initialId; + this.replyId = supplyReplyId.applyAsLong(initialId); + this.affinity = affinity; + this.authorization = authorization; + } + + private void onHttpMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onHttpBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onHttpData(data); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onHttpEnd(end); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onHttpFlush(flush); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onHttpAbort(abort); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onHttpWindow(window); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onHttpReset(reset); + break; + default: + break; + } + } + + private void onHttpBegin( + BeginFW begin) + { + final long sequence = begin.sequence(); + final long acknowledge = begin.acknowledge(); + final long traceId = begin.traceId(); + final long authorization = begin.authorization(); + final long affinity = begin.affinity(); + final OctetsFW extension = begin.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + assert acknowledge >= initialAck; + + initialSeq = sequence; + initialAck = acknowledge; + state = OpenapiState.openingInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiBegin(traceId, extension); + } + + private void onHttpData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final long authorization = data.authorization(); + final long budgetId = data.budgetId(); + final int reserved = data.reserved(); + final int flags = data.flags(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + openapi.doOpenapiData(traceId, authorization, budgetId, reserved, flags, payload, extension); + } + + private void onHttpEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiEnd(traceId, extension); + } + + private void onHttpFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence + reserved; + + assert initialAck <= initialSeq; + + openapi.doOpenapiFlush(traceId, reserved, extension); + } + + private void onHttpAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + final OctetsFW extension = abort.extension(); + + assert acknowledge <= sequence; + assert sequence >= initialSeq; + + initialSeq = sequence; + state = OpenapiState.closeInitial(state); + + assert initialAck <= initialSeq; + + openapi.doOpenapiAbort(traceId, extension); + } + + private void onHttpReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final int maximum = reset.maximum(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + state = OpenapiState.closeReply(state); + + assert replyAck <= replySeq; + + cleanup(traceId); + } + + private void onHttpWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert sequence <= replySeq; + assert acknowledge >= replyAck; + assert maximum >= replyMax; + + replyAck = acknowledge; + replyMax = maximum; + replyPad = padding; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + openapi.doOpenapiWindow(traceId, acknowledge, budgetId, padding); + } + + private void doHttpBegin( + long traceId, + OctetsFW extension) + { + state = OpenapiState.openingReply(state); + + doBegin(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, affinity, extension); + } + + private void doHttpData( + long traceId, + int flag, + int reserved, + OctetsFW payload, + Flyweight extension) + { + doData(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, flag, reserved, payload, extension); + + replySeq += reserved; + } + + private void doHttpFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, replyBudgetId, reserved, extension); + + replySeq += reserved; + } + + private void doHttpEnd( + long traceId, + OctetsFW extension) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doEnd(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, extension); + } + + state = OpenapiState.closeReply(state); + } + + private void doHttpAbort( + long traceId) + { + if (OpenapiState.replyOpening(state) && !OpenapiState.replyClosed(state)) + { + doAbort(sender, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + } + + state = OpenapiState.closeInitial(state); + } + + private void doHttpReset( + long traceId) + { + if (!OpenapiState.initialClosed(state)) + { + state = OpenapiState.closeInitial(state); + + doReset(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, EMPTY_OCTETS); + } + } + + private void doHttpWindow( + long authorization, + long traceId, + long budgetId, + int padding) + { + initialAck = openapi.initialAck; + initialMax = openapi.initialMax; + + doWindow(sender, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, padding); + } + + private void cleanup( + long traceId) + { + doHttpReset(traceId); + doHttpAbort(traceId); + + openapi.cleanup(traceId); + } + } + + private final class OpenapiStream + { + private final HttpStream delegate; + private final String operationId; + private final long originId; + private final long routedId; + private final long apiId; + private final long authorization; + + private final long initialId; + private final long replyId; + private MessageConsumer receiver; + + private int state; + + private long initialSeq; + private long initialAck; + private int initialMax; + private long initialBud; + + private long replySeq; + private long replyAck; + private int replyMax; + private int replyPad; + + private OpenapiStream( + HttpStream delegate, + long originId, + long routedId, + long authorization, + long apiId, + String operationId) + { + this.delegate = delegate; + this.originId = originId; + this.routedId = routedId; + this.apiId = apiId; + this.receiver = MessageConsumer.NOOP; + this.authorization = authorization; + this.initialId = supplyInitialId.applyAsLong(routedId); + this.replyId = supplyReplyId.applyAsLong(initialId); + this.operationId = operationId; + } + + private void onOpenapiMessage( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + switch (msgTypeId) + { + case BeginFW.TYPE_ID: + final BeginFW begin = beginRO.wrap(buffer, index, index + length); + onOpenapiBegin(begin); + break; + case DataFW.TYPE_ID: + final DataFW data = dataRO.wrap(buffer, index, index + length); + onOpenapiData(data); + break; + case FlushFW.TYPE_ID: + final FlushFW flush = flushRO.wrap(buffer, index, index + length); + onOpenapiFlush(flush); + break; + case EndFW.TYPE_ID: + final EndFW end = endRO.wrap(buffer, index, index + length); + onOpenapiEnd(end); + break; + case AbortFW.TYPE_ID: + final AbortFW abort = abortRO.wrap(buffer, index, index + length); + onOpenapiAbort(abort); + break; + case ResetFW.TYPE_ID: + final ResetFW reset = resetRO.wrap(buffer, index, index + length); + onOpenapiReset(reset); + break; + case WindowFW.TYPE_ID: + final WindowFW window = windowRO.wrap(buffer, index, index + length); + onOpenapiWindow(window); + break; + default: + break; + } + } + + private void onOpenapiBegin( + BeginFW begin) + { + final long traceId = begin.traceId(); + final OctetsFW extension = begin.extension(); + + state = OpenapiState.openingReply(state); + + final OpenapiBeginExFW openapiBeginEx = extension.get(openapiBeginExRO::tryWrap); + + delegate.doHttpBegin(traceId, openapiBeginEx.extension()); + } + + private void onOpenapiData( + DataFW data) + { + final long sequence = data.sequence(); + final long acknowledge = data.acknowledge(); + final long traceId = data.traceId(); + final int flags = data.flags(); + final int reserved = data.reserved(); + final OctetsFW payload = data.payload(); + final OctetsFW extension = data.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doHttpData(traceId, flags, reserved, payload, extension); + } + + private void onOpenapiFlush( + FlushFW flush) + { + final long sequence = flush.sequence(); + final long acknowledge = flush.acknowledge(); + final long traceId = flush.traceId(); + final int reserved = flush.reserved(); + final OctetsFW extension = flush.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence + reserved; + + assert replyAck <= replySeq; + assert replySeq <= replyAck + replyMax; + + delegate.doHttpFlush(traceId, reserved, extension); + } + + private void onOpenapiEnd( + EndFW end) + { + final long sequence = end.sequence(); + final long acknowledge = end.acknowledge(); + final long traceId = end.traceId(); + final OctetsFW extension = end.extension(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doHttpEnd(traceId, extension); + } + + private void onOpenapiAbort( + AbortFW abort) + { + final long sequence = abort.sequence(); + final long acknowledge = abort.acknowledge(); + final long traceId = abort.traceId(); + + assert acknowledge <= sequence; + assert sequence >= replySeq; + + replySeq = sequence; + state = OpenapiState.closingReply(state); + + assert replyAck <= replySeq; + + delegate.doHttpAbort(traceId); + } + + private void onOpenapiReset( + ResetFW reset) + { + final long sequence = reset.sequence(); + final long acknowledge = reset.acknowledge(); + final long traceId = reset.traceId(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + + delegate.initialAck = acknowledge; + state = OpenapiState.closeInitial(state); + + assert delegate.initialAck <= delegate.initialSeq; + + delegate.doHttpReset(traceId); + } + + + private void onOpenapiWindow( + WindowFW window) + { + final long sequence = window.sequence(); + final long acknowledge = window.acknowledge(); + final int maximum = window.maximum(); + final long authorization = window.authorization(); + final long traceId = window.traceId(); + final long budgetId = window.budgetId(); + final int padding = window.padding(); + + assert acknowledge <= sequence; + assert acknowledge >= delegate.initialAck; + assert maximum >= delegate.initialMax; + + initialAck = acknowledge; + initialMax = maximum; + initialBud = budgetId; + state = OpenapiState.openInitial(state); + + assert initialAck <= initialSeq; + + delegate.doHttpWindow(authorization, traceId, budgetId, padding); + } + + private void doOpenapiBegin( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialOpening(state)) + { + assert state == 0; + + final OpenapiBeginExFW openapiBeginEx = openapiBeginExRW + .wrap(extBuffer, 0, extBuffer.capacity()) + .typeId(openapiTypeId) + .apiId(apiId) + .operationId(operationId) + .extension(extension) + .build(); + + this.receiver = newStream(this::onOpenapiMessage, originId, routedId, initialId, initialSeq, + initialAck, initialMax, traceId, authorization, 0L, openapiBeginEx); + state = OpenapiState.openingInitial(state); + } + } + + private void doOpenapiData( + long traceId, + long authorization, + long budgetId, + int reserved, + int flags, + OctetsFW payload, + Flyweight extension) + { + doData(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, budgetId, flags, reserved, payload, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doOpenapiFlush( + long traceId, + int reserved, + OctetsFW extension) + { + doFlush(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, initialBud, reserved, extension); + + initialSeq += reserved; + + assert initialSeq <= initialAck + initialMax; + } + + private void doOpenapiEnd( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doEnd(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doOpenapiAbort( + long traceId, + OctetsFW extension) + { + if (!OpenapiState.initialClosed(state)) + { + doAbort(receiver, originId, routedId, initialId, initialSeq, initialAck, initialMax, + traceId, authorization, extension); + + state = OpenapiState.closeInitial(state); + } + } + + private void doOpenapiReset( + long traceId) + { + if (!OpenapiState.replyClosed(state)) + { + doReset(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, EMPTY_OCTETS); + + state = OpenapiState.closeReply(state); + } + } + + private void doOpenapiWindow( + long traceId, + long authorization, + long budgetId, + int padding) + { + replyAck = Math.max(delegate.replyAck - replyPad, 0); + replyMax = delegate.replyMax; + + doWindow(receiver, originId, routedId, replyId, replySeq, replyAck, replyMax, + traceId, authorization, budgetId, padding + replyPad); + } + + private void cleanup( + long traceId) + { + doOpenapiAbort(traceId, EMPTY_OCTETS); + doOpenapiReset(traceId); + } + } + + private MessageConsumer newStream( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + final MessageConsumer receiver = + streamFactory.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + + return receiver; + } + + private void doBegin( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long affinity, + Flyweight extension) + { + final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .affinity(affinity) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof()); + } + + private void doData( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int flags, + int reserved, + OctetsFW payload, + Flyweight extension) + { + final DataFW frame = dataRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .flags(flags) + .budgetId(budgetId) + .reserved(reserved) + .payload(payload) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(frame.typeId(), frame.buffer(), frame.offset(), frame.sizeof()); + } + + private void doFlush( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int reserved, + OctetsFW extension) + { + final FlushFW flush = flushRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .reserved(reserved) + .extension(extension) + .build(); + + receiver.accept(flush.typeId(), flush.buffer(), flush.offset(), flush.sizeof()); + } + + private void doEnd( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final EndFW end = endRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(end.typeId(), end.buffer(), end.offset(), end.sizeof()); + } + + private void doAbort( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + OctetsFW extension) + { + final AbortFW abort = abortRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension) + .build(); + + receiver.accept(abort.typeId(), abort.buffer(), abort.offset(), abort.sizeof()); + } + + private void doWindow( + MessageConsumer sender, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + long budgetId, + int padding) + { + final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .budgetId(budgetId) + .padding(padding) + .build(); + + sender.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof()); + } + + private void doReset( + MessageConsumer receiver, + long originId, + long routedId, + long streamId, + long sequence, + long acknowledge, + int maximum, + long traceId, + long authorization, + Flyweight extension) + { + final ResetFW reset = resetRW.wrap(writeBuffer, 0, writeBuffer.capacity()) + .originId(originId) + .routedId(routedId) + .streamId(streamId) + .sequence(sequence) + .acknowledge(acknowledge) + .maximum(maximum) + .traceId(traceId) + .authorization(authorization) + .extension(extension.buffer(), extension.offset(), extension.sizeof()) + .build(); + + receiver.accept(reset.typeId(), reset.buffer(), reset.offset(), reset.sizeof()); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java new file mode 100644 index 0000000000..687aae313f --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiState.java @@ -0,0 +1,134 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +public final class OpenapiState +{ + private static final int INITIAL_OPENING = 0x10; + private static final int INITIAL_OPENED = 0x20; + private static final int INITIAL_CLOSING = 0x40; + private static final int INITIAL_CLOSED = 0x80; + private static final int REPLY_OPENING = 0x01; + private static final int REPLY_OPENED = 0x02; + private static final int REPLY_CLOSING = 0x04; + private static final int REPLY_CLOSED = 0x08; + + static int openingInitial( + int state) + { + return state | INITIAL_OPENING; + } + + static int openInitial( + int state) + { + return openingInitial(state) | INITIAL_OPENED; + } + + static int closingInitial( + int state) + { + return state | INITIAL_CLOSING; + } + + static int closeInitial( + int state) + { + return closingInitial(state) | INITIAL_CLOSED; + } + + static boolean initialOpening( + int state) + { + return (state & INITIAL_OPENING) != 0; + } + + static boolean initialOpened( + int state) + { + return (state & INITIAL_OPENED) != 0; + } + + static boolean initialClosing( + int state) + { + return (state & INITIAL_CLOSING) != 0; + } + + static boolean initialClosed( + int state) + { + return (state & INITIAL_CLOSED) != 0; + } + + static boolean closed( + int state) + { + return initialClosed(state) && replyClosed(state); + } + + static int openingReply( + int state) + { + return state | REPLY_OPENING; + } + + static int openReply( + int state) + { + return state | REPLY_OPENED; + } + + static boolean replyOpening( + int state) + { + return (state & REPLY_OPENING) != 0; + } + + static boolean replyOpened( + int state) + { + return (state & REPLY_OPENED) != 0; + } + + static int closingReply( + int state) + { + return state | REPLY_CLOSING; + } + + static boolean replyClosing( + int state) + { + return (state & REPLY_CLOSING) != 0; + } + + static int closeReply( + int state) + { + return closingReply(state) | REPLY_CLOSED; + } + + static boolean replyClosed( + int state) + { + return (state & REPLY_CLOSED) != 0; + } + + private OpenapiState() + { + // utility + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java new file mode 100644 index 0000000000..706267b5ef --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiStreamFactory.java @@ -0,0 +1,27 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import io.aklivity.zilla.runtime.engine.binding.BindingHandler; +import io.aklivity.zilla.runtime.engine.config.BindingConfig; + +public interface OpenapiStreamFactory extends BindingHandler +{ + void attach( + BindingConfig binding); + + void detach( + long bindingId); +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationView.java new file mode 100644 index 0000000000..e85a07d06c --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationView.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiResponseByContentType; + +public final class OpenapiOperationView +{ + public static final String DEFAULT = "default"; + + private final OpenapiOperation operation; + private final boolean hasResponses; + + private OpenapiOperationView( + OpenapiOperation operation) + { + this.operation = operation; + this.hasResponses = initHasResponses(); + } + + public Map responsesByStatus() + { + return operation.responses; + } + + public boolean hasResponses() + { + return hasResponses; + } + + private boolean initHasResponses() + { + boolean result = false; + if (operation != null && operation.responses != null) + { + for (Map.Entry response0 : operation.responses.entrySet()) + { + String status = response0.getKey(); + OpenapiResponseByContentType response1 = response0.getValue(); + if (!(DEFAULT.equals(status)) && response1.content != null) + { + result = true; + break; + } + } + } + return result; + } + + public static OpenapiOperationView of( + OpenapiOperation operation) + { + return new OpenapiOperationView(operation); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationsView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationsView.java new file mode 100644 index 0000000000..fe735b269d --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiOperationsView.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.agrona.collections.Object2ObjectHashMap; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; + +public final class OpenapiOperationsView +{ + private final Map> operationsByPath; + private final boolean hasResponses; + + public boolean hasResponses() + { + return this.hasResponses; + } + + public OpenapiOperationView operation( + String pathName, + String methodName) + { + return operationsByPath.get(pathName).get(methodName); + } + + public static OpenapiOperationsView of( + Map paths) + { + return new OpenapiOperationsView(paths); + } + + private OpenapiOperationsView( + Map paths) + { + this.operationsByPath = new Object2ObjectHashMap<>(); + boolean hasResponses = false; + for (String pathName : paths.keySet()) + { + OpenapiPathView path = OpenapiPathView.of(paths.get(pathName)); + for (String methodName : path.methods().keySet()) + { + OpenapiOperationView operation = OpenapiOperationView.of(path.methods().get(methodName)); + hasResponses |= operation.hasResponses(); + if (operationsByPath.containsKey(pathName)) + { + operationsByPath.get(pathName).put(methodName, operation); + } + else + { + Map operationsPerMethod = new LinkedHashMap<>(); + operationsPerMethod.put(methodName, operation); + operationsByPath.put(pathName, operationsPerMethod); + } + } + } + this.hasResponses = hasResponses; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiPathView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiPathView.java new file mode 100644 index 0000000000..1caac5e5b1 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiPathView.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import static java.util.Collections.unmodifiableMap; + +import java.util.LinkedHashMap; +import java.util.Map; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiOperation; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; + +public final class OpenapiPathView +{ + private final Map methods; + + public Map methods() + { + return methods; + } + + public static OpenapiPathView of( + OpenapiPathItem pathItem) + { + return new OpenapiPathView(pathItem); + } + + private OpenapiPathView( + OpenapiPathItem pathItem) + { + Map methods = new LinkedHashMap<>(); + putIfNotNull(methods, "GET", pathItem.get); + putIfNotNull(methods, "PUT", pathItem.put); + putIfNotNull(methods, "POST", pathItem.post); + putIfNotNull(methods, "DELETE", pathItem.delete); + putIfNotNull(methods, "OPTIONS", pathItem.options); + putIfNotNull(methods, "HEAD", pathItem.head); + putIfNotNull(methods, "PATCH", pathItem.patch); + putIfNotNull(methods, "TRACE", pathItem.trace); + this.methods = unmodifiableMap(methods); + } + + private static void putIfNotNull( + Map methods, + String method, + OpenapiOperation operation) + { + if (operation != null) + { + methods.put(method, operation); + } + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiResolvable.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiResolvable.java new file mode 100644 index 0000000000..dd6c47b837 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiResolvable.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class OpenapiResolvable +{ + private final Map map; + private final Matcher matcher; + + protected String key; + + public OpenapiResolvable( + Map map, + String regex) + { + this.map = map; + this.matcher = Pattern.compile(regex).matcher(""); + } + + protected T resolveRef( + String ref) + { + T result = null; + if (matcher.reset(ref).matches()) + { + key = matcher.group(1); + result = map.get(key); + } + return result; + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiSchemaView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiSchemaView.java new file mode 100644 index 0000000000..366bf5ad78 --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiSchemaView.java @@ -0,0 +1,95 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.util.List; +import java.util.Map; + +import jakarta.json.bind.annotation.JsonbPropertyOrder; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiItem; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiSchema; + +@JsonbPropertyOrder({ + "type", + "items", + "properties", + "required" +}) +public final class OpenapiSchemaView extends OpenapiResolvable +{ + private static final String ARRAY_TYPE = "array"; + + private final OpenapiSchema schema; + private final Map schemas; + private final OpenapiSchema schemaRef; + + private OpenapiSchemaView( + Map schemas, + OpenapiSchema schema) + { + super(schemas, "#/components/schemas/(\\w+)"); + OpenapiSchema schemaRef = null; + if (schema.ref != null) + { + schemaRef = new OpenapiSchema(); + schemaRef.ref = schema.ref; + schema = resolveRef(schema.ref); + } + else if (ARRAY_TYPE.equals(schema.type) && schema.items != null && schema.items.ref != null) + { + schema.items = resolveRef(schema.items.ref); + } + this.schemaRef = schemaRef; + this.schemas = schemas; + this.schema = schema; + } + + public String refKey() + { + return key; + } + public OpenapiSchema ref() + { + return schemaRef; + } + + public String getType() + { + return schema.type; + } + + public OpenapiSchemaView getItems() + { + return schema.items == null ? null : OpenapiSchemaView.of(schemas, schema.items); + } + + public Map getProperties() + { + return schema.properties; + } + + public List getRequired() + { + return schema.required; + } + + public static OpenapiSchemaView of( + Map schemas, + OpenapiSchema schema) + { + return new OpenapiSchemaView(schemas, schema); + } +} diff --git a/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiServerView.java b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiServerView.java new file mode 100644 index 0000000000..1a238ee2da --- /dev/null +++ b/incubator/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/view/OpenapiServerView.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.view; + +import java.net.URI; + +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer; + +public final class OpenapiServerView +{ + private URI url; + + private OpenapiServerView( + OpenapiServer server) + { + this.url = URI.create(server.url); + } + + public URI url() + { + return url; + } + + public static OpenapiServerView of( + OpenapiServer server) + { + return new OpenapiServerView(server); + } +} diff --git a/incubator/binding-openapi/src/main/moditect/module-info.java b/incubator/binding-openapi/src/main/moditect/module-info.java new file mode 100644 index 0000000000..9e8001e5dd --- /dev/null +++ b/incubator/binding-openapi/src/main/moditect/module-info.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.binding.openapi +{ + requires org.leadpony.justify; + + requires io.aklivity.zilla.runtime.engine; + requires io.aklivity.zilla.runtime.binding.http; + requires io.aklivity.zilla.runtime.binding.tcp; + requires io.aklivity.zilla.runtime.binding.tls; + requires io.aklivity.zilla.runtime.catalog.inline; + requires io.aklivity.zilla.runtime.guard.jwt; + requires io.aklivity.zilla.runtime.vault.filesystem; + requires io.aklivity.zilla.runtime.model.core; + requires io.aklivity.zilla.runtime.model.json; + + exports io.aklivity.zilla.runtime.binding.openapi.config; + + opens io.aklivity.zilla.runtime.binding.openapi.internal.model; + opens io.aklivity.zilla.runtime.binding.openapi.internal.view; + + provides io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi + with io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiCompositeBindingAdapter; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiOptionsConfigAdapter; +} diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi new file mode 100644 index 0000000000..fa663f0820 --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.binding.BindingFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiBindingFactorySpi diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi new file mode 100644 index 0000000000..499d92471c --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiCompositeBindingAdapter diff --git a/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..52d34a108e --- /dev/null +++ b/incubator/binding-openapi/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.binding.openapi.internal.config.OpenapiOptionsConfigAdapter diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java new file mode 100644 index 0000000000..1ea1d03090 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/OpenapiConfigurationTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal; + +import static io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfiguration.OPENAPI_TARGET_ROUTE_ID; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class OpenapiConfigurationTest +{ + public static final String OPENAPI_TARGET_ROUTE_ID_NAME = "zilla.binding.openapi.target.route.id"; + + @Test + public void shouldVerifyConstants() throws Exception + { + assertEquals(OPENAPI_TARGET_ROUTE_ID.name(), OPENAPI_TARGET_ROUTE_ID_NAME); + } +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..d67df631e7 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiOptionsConfigAdapterTest.java @@ -0,0 +1,145 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.config; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.Arrays.asList; +import static java.util.function.Function.identity; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiConfig; +import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi; +import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiPathItem; +import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tls.config.TlsOptionsConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.specs.binding.openapi.OpenapiSpecs; + +public class OpenapiOptionsConfigAdapterTest +{ + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + @Mock + private ConfigAdapterContext context; + + private Jsonb jsonb; + + @Before + public void initJson() throws IOException + { + try (InputStream resource = OpenapiSpecs.class.getResourceAsStream("config/openapi/petstore.yaml")) + { + String content = new String(resource.readAllBytes(), UTF_8); + Mockito.doReturn(content).when(context).readURL("openapi/petstore.yaml"); + OptionsConfigAdapter adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter.adaptType("openapi"); + JsonbConfig config = new JsonbConfig() + .withAdapters(adapter); + jsonb = JsonbBuilder.create(config); + } + } + + @Test + public void shouldReadOptions() + { + String text = + "{" + + " \"tls\": {" + + " \"keys\": [" + + " \"localhost\"" + + " ]," + + " \"alpn\": [" + + " \"localhost\"" + + " ]" + + " }," + + " \"http\": {" + + " \"authorization\": {" + + " \"test0\": {" + + " \"credentials\": {" + + " \"cookies\": {" + + " \"access_token\": \"{credentials}\"" + + " }," + + " \"headers\": {" + + " \"authorization\": \"Bearer {credentials}\"" + + " }," + + " \"query\": {" + + " \"access_token\": \"{credentials}\"" + + " }" + + " }" + + " }" + + " }" + + " }," + + " \"specs\": {" + + " \"openapi-id\": \"openapi/petstore.yaml\"" + + " }" + + " }"; + + OpenapiOptionsConfig options = jsonb.fromJson(text, OpenapiOptionsConfig.class); + OpenapiConfig openapi = options.openapis.stream().findFirst().get(); + OpenapiPathItem path = openapi.openapi.paths.get("/pets"); + + assertThat(options, not(nullValue())); + assertThat(path.post, not(nullValue())); + assertThat(options.tls, not(nullValue())); + assertThat(options.http, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + String expected = "{\"tcp\":{\"host\":\"localhost\",\"port\":8080},\"tls\":{\"sni\":[\"example.net\"]}," + + "\"specs\":{\"openapi-id\":\"openapi/petstore.yaml\"}}"; + + TcpOptionsConfig tcp = TcpOptionsConfig.builder() + .inject(identity()) + .host("localhost") + .ports(new int[] { 8080 }) + .build(); + + TlsOptionsConfig tls = TlsOptionsConfig.builder() + .inject(identity()) + .sni(asList("example.net")) + .build(); + + OpenapiOptionsConfig options = new OpenapiOptionsConfig(tcp, tls, null, asList( + new OpenapiConfig("openapi-id", 1L, "openapi/petstore.yaml", new Openapi()))); + + String text = jsonb.toJson(options); + + assertThat(text, not(nullValue())); + assertEquals(expected, text); + } +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java new file mode 100644 index 0000000000..db61283ebf --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiClientIT.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import static io.aklivity.zilla.runtime.binding.openapi.internal.OpenapiConfigurationTest.OPENAPI_TARGET_ROUTE_ID_NAME; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.ScriptProperty; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class OpenapiClientIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http") + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config") + .external("http0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${openapi}/create.pet/client", + "${http}/create.pet/server" + }) + @ScriptProperty("serverAddress \"zilla://streams/http0\"") + @Configure(name = OPENAPI_TARGET_ROUTE_ID_NAME, value = "4294967298") + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + +} diff --git a/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java new file mode 100644 index 0000000000..6fc18b1176 --- /dev/null +++ b/incubator/binding-openapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/internal/streams/OpenapiServerIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.openapi.internal.streams; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; + +public class OpenapiServerIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("http", "io/aklivity/zilla/specs/binding/openapi/streams/http") + .addScriptRoot("openapi", "io/aklivity/zilla/specs/binding/openapi/streams/openapi"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/openapi/config") + .external("openapi0") + .clean(); + + @Rule + public final TestRule chain = outerRule(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${http}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreatePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server-secure.yaml") + @Specification({ + "${http}/create.pet/client", + "${openapi}/create.pet/server" + }) + public void shouldCreateSecurePet() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${http}/reject.non.composite.origin/client" + }) + public void shouldRejectNonCompositeOrigin() throws Exception + { + k3po.finish(); + } +} diff --git a/incubator/catalog-inline.spec/pom.xml b/incubator/catalog-inline.spec/pom.xml index ad12639c11..6f2dfa4bbf 100644 --- a/incubator/catalog-inline.spec/pom.xml +++ b/incubator/catalog-inline.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/catalog-inline/pom.xml b/incubator/catalog-inline/pom.xml index f4616122d4..0dcc934199 100644 --- a/incubator/catalog-inline/pom.xml +++ b/incubator/catalog-inline/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java b/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java index d291d67efb..066f928be0 100644 --- a/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java +++ b/incubator/catalog-inline/src/main/java/io/aklivity/zilla/runtime/catalog/inline/internal/InlineCatalogHandler.java @@ -51,7 +51,7 @@ public int register( public String resolve( int schemaId) { - return schemas.containsKey(schemaId) ? schemas.get(schemaId) : null; + return schemas.getOrDefault(schemaId, null); } @Override @@ -60,7 +60,7 @@ public int resolve( String version) { String key = subject + version; - return schemaIds.containsKey(key) ? schemaIds.get(key) : NO_SCHEMA_ID; + return schemaIds.getOrDefault(key, NO_SCHEMA_ID); } private int generateCRC32C( diff --git a/incubator/catalog-schema-registry.spec/pom.xml b/incubator/catalog-schema-registry.spec/pom.xml index f6e0347feb..f2016b5ee3 100644 --- a/incubator/catalog-schema-registry.spec/pom.xml +++ b/incubator/catalog-schema-registry.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml @@ -63,6 +63,23 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core schema_registry + io.aklivity.zilla.specs.catalog.schema.registry.internal.types + + + + + validate + generate + + + + com.mycila license-maven-plugin @@ -86,6 +103,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/specs/catalog/schema/registry/internal/types/**/*.class + BUNDLE diff --git a/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl new file mode 100644 index 0000000000..df61e3605c --- /dev/null +++ b/incubator/catalog-schema-registry.spec/src/main/resources/META-INF/zilla/schema_registry.idl @@ -0,0 +1,36 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +scope schema_registry +{ + scope event + { + enum SchemaRegistryEventType (uint8) + { + REMOTE_ACCESS_REJECTED (1) + } + + struct SchemaRegistryRemoteAccessRejected extends core::event::Event + { + string8 method; + string16 url; + int16 status; + } + + union SchemaRegistryEvent switch (SchemaRegistryEventType) + { + case REMOTE_ACCESS_REJECTED: SchemaRegistryRemoteAccessRejected remoteAccessRejected; + } + } +} diff --git a/incubator/catalog-schema-registry/pom.xml b/incubator/catalog-schema-registry/pom.xml index 6da63502c2..461e4292e2 100644 --- a/incubator/catalog-schema-registry/pom.xml +++ b/incubator/catalog-schema-registry/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml @@ -24,7 +24,7 @@ 11 11 - 0.90 + 0.80 0 @@ -71,16 +71,12 @@ org.jasig.maven maven-notice-plugin - - com.mycila - license-maven-plugin - ${project.groupId} flyweight-maven-plugin ${project.version} - internal + core schema_registry internal io.aklivity.zilla.runtime.catalog.schema.registry.internal.types @@ -91,6 +87,10 @@ + + com.mycila + license-maven-plugin + maven-checkstyle-plugin diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java index 8affce6dc5..c7ebe83402 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalog.java @@ -40,7 +40,7 @@ public String name() public CatalogContext supply( EngineContext context) { - return new SchemaRegistryCatalogContext(); + return new SchemaRegistryCatalogContext(context); } @Override diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java index cf5c8eb7b6..1dbfd3c22f 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogContext.java @@ -15,16 +15,25 @@ package io.aklivity.zilla.runtime.catalog.schema.registry.internal; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.config.CatalogConfig; public class SchemaRegistryCatalogContext implements CatalogContext { + private final EngineContext context; + + public SchemaRegistryCatalogContext( + EngineContext context) + { + this.context = context; + } + @Override public CatalogHandler attach( CatalogConfig catalog) { - return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options)); + return new SchemaRegistryCatalogHandler(SchemaRegistryOptionsConfig.class.cast(catalog.options), context, catalog.id); } } diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java index c8bc750709..88f47fd777 100644 --- a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryCatalogHandler.java @@ -30,6 +30,7 @@ import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.serializer.RegisterSchemaRequest; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.SchemaRegistryPrefixFW; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer; @@ -51,9 +52,13 @@ public class SchemaRegistryCatalogHandler implements CatalogHandler private final Int2ObjectCache schemas; private final Int2ObjectCache schemaIds; private final long maxAgeMillis; + private final SchemaRegistryEventContext event; + private final long catalogId; public SchemaRegistryCatalogHandler( - SchemaRegistryOptionsConfig config) + SchemaRegistryOptionsConfig config, + EngineContext context, + long catalogId) { this.baseUrl = config.url; this.client = HttpClient.newHttpClient(); @@ -62,6 +67,8 @@ public SchemaRegistryCatalogHandler( this.schemas = new Int2ObjectCache<>(1, 1024, i -> {}); this.schemaIds = new Int2ObjectCache<>(1, 1024, i -> {}); this.maxAgeMillis = config.maxAge.toMillis(); + this.event = new SchemaRegistryEventContext(context); + this.catalogId = catalogId; } @Override @@ -209,12 +216,18 @@ private String sendHttpRequest( try { - HttpResponse response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); - return response.statusCode() == 200 ? response.body() : null; + HttpResponse httpResponse = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); + boolean success = httpResponse.statusCode() == 200; + String responseBody = success ? httpResponse.body() : null; + if (!success) + { + event.remoteAccessRejected(catalogId, httpRequest, httpResponse.statusCode()); + } + return responseBody; } catch (Exception ex) { - ex.printStackTrace(System.out); + event.remoteAccessRejected(catalogId, httpRequest, 0); } return null; } diff --git a/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java new file mode 100644 index 0000000000..42770a31b1 --- /dev/null +++ b/incubator/catalog-schema-registry/src/main/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryEventContext.java @@ -0,0 +1,64 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.catalog.schema.registry.internal; + +import java.net.http.HttpRequest; +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.catalog.schema.registry.internal.types.event.SchemaRegistryEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class SchemaRegistryEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final SchemaRegistryEventFW.Builder schemaRegistryEventRW = new SchemaRegistryEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int schemaRegistryTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public SchemaRegistryEventContext( + EngineContext context) + { + this.schemaRegistryTypeId = context.supplyTypeId(SchemaRegistryCatalog.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void remoteAccessRejected( + long catalogId, + HttpRequest httpRequest, + int status) + { + SchemaRegistryEventFW event = schemaRegistryEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .remoteAccessRejected(e -> e + .timestamp(clock.millis()) + .traceId(0L) + .namespacedId(catalogId) + .method(httpRequest.method()) + .url(httpRequest.uri().toString()) + .status((short) status) + ) + .build(); + eventWriter.accept(schemaRegistryTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java index f65af539c1..f04ed60d0b 100644 --- a/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java +++ b/incubator/catalog-schema-registry/src/test/java/io/aklivity/zilla/runtime/catalog/schema/registry/internal/SchemaRegistryIT.java @@ -20,6 +20,7 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.Assert.assertEquals; import static org.junit.rules.RuleChain.outerRule; +import static org.mockito.Mockito.mock; import java.time.Duration; @@ -35,6 +36,7 @@ import org.kaazing.k3po.junit.rules.K3poRule; import io.aklivity.zilla.runtime.catalog.schema.registry.internal.config.SchemaRegistryOptionsConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler; import io.aklivity.zilla.runtime.engine.model.function.ValueConsumer; @@ -49,6 +51,7 @@ public class SchemaRegistryIT public final TestRule chain = outerRule(k3po).around(timeout); private SchemaRegistryOptionsConfig config; + private EngineContext context = mock(EngineContext.class); @Before public void setup() @@ -69,7 +72,7 @@ public void shouldResolveSchemaViaSchemaId() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); String schema = catalog.resolve(9); @@ -88,7 +91,7 @@ public void shouldResolveSchemaViaSubjectVersion() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); int schemaId = catalog.resolve("items-snapshots-value", "latest"); @@ -109,7 +112,7 @@ public void shouldRegisterSchema() throws Exception String schema = "{\"type\": \"record\",\"name\": \"test\",\"fields\":[{\"type\": \"string\",\"name\": \"field1\"}," + "{\"type\": \"com.acme.Referenced\",\"name\": \"int\"}]}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); int schemaId = catalog.register("items-snapshots-value", "avro", schema); @@ -128,7 +131,7 @@ public void shouldResolveSchemaViaSchemaIdFromCache() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); catalog.resolve(9); @@ -149,7 +152,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception "{\"name\":\"status\",\"type\":\"string\"}]," + "\"name\":\"Event\",\"namespace\":\"io.aklivity.example\",\"type\":\"record\"}"; - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); catalog.resolve(catalog.resolve("items-snapshots-value", "latest")); @@ -167,7 +170,7 @@ public void shouldResolveSchemaViaSubjectVersionFromCache() throws Exception @Test public void shouldVerifyMaxPadding() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); assertEquals(5, catalog.encodePadding()); } @@ -175,7 +178,7 @@ public void shouldVerifyMaxPadding() @Test public void shouldVerifyEncodedData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); @@ -191,7 +194,7 @@ public void shouldVerifyEncodedData() public void shouldResolveSchemaIdAndProcessData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); @@ -207,7 +210,7 @@ public void shouldResolveSchemaIdAndProcessData() @Test public void shouldResolveSchemaIdFromData() { - SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config); + SchemaRegistryCatalogHandler catalog = new SchemaRegistryCatalogHandler(config, context, 0L); DirectBuffer data = new UnsafeBuffer(); diff --git a/incubator/command-dump/pom.xml b/incubator/command-dump/pom.xml index a10451bd5d..d63f01abcc 100644 --- a/incubator/command-dump/pom.xml +++ b/incubator/command-dump/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/command-generate/pom.xml b/incubator/command-generate/pom.xml index 387c916b72..aa3ea83e14 100644 --- a/incubator/command-generate/pom.xml +++ b/incubator/command-generate/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/command-log/pom.xml b/incubator/command-log/pom.xml index 5fa16a3ccb..4fbf297c72 100644 --- a/incubator/command-log/pom.xml +++ b/incubator/command-log/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/command-tune/pom.xml b/incubator/command-tune/pom.xml index 162320e2d3..27bd604857 100644 --- a/incubator/command-tune/pom.xml +++ b/incubator/command-tune/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/exporter-otlp.spec/pom.xml b/incubator/exporter-otlp.spec/pom.xml index ecd36befc6..4aec1f1e66 100644 --- a/incubator/exporter-otlp.spec/pom.xml +++ b/incubator/exporter-otlp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/exporter-otlp/pom.xml b/incubator/exporter-otlp/pom.xml index 212ced1b20..9900771412 100644 --- a/incubator/exporter-otlp/pom.xml +++ b/incubator/exporter-otlp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/exporter-stdout.spec/COPYRIGHT b/incubator/exporter-stdout.spec/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/exporter-stdout.spec/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/exporter-stdout.spec/LICENSE b/incubator/exporter-stdout.spec/LICENSE new file mode 100644 index 0000000000..f3cf11c3ad --- /dev/null +++ b/incubator/exporter-stdout.spec/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/exporter-stdout.spec/NOTICE b/incubator/exporter-stdout.spec/NOTICE new file mode 100644 index 0000000000..cf99769e41 --- /dev/null +++ b/incubator/exporter-stdout.spec/NOTICE @@ -0,0 +1,18 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + agrona under The Apache License, Version 2.0 + ICU4J under Unicode/ICU License + Jakarta JSON Processing API under Eclipse Public License 2.0 or GNU General Public License, version 2 with the GNU Classpath Exception + org.leadpony.justify under The Apache Software License, Version 2.0 + zilla::specs::engine.spec under The Apache Software License, Version 2.0 + diff --git a/incubator/exporter-stdout.spec/NOTICE.template b/incubator/exporter-stdout.spec/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/exporter-stdout.spec/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/exporter-stdout.spec/mvnw b/incubator/exporter-stdout.spec/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/exporter-stdout.spec/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/exporter-stdout.spec/mvnw.cmd b/incubator/exporter-stdout.spec/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/exporter-stdout.spec/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/exporter-stdout.spec/pom.xml b/incubator/exporter-stdout.spec/pom.xml new file mode 100644 index 0000000000..1ec0a89dd1 --- /dev/null +++ b/incubator/exporter-stdout.spec/pom.xml @@ -0,0 +1,180 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + exporter-stdout.spec + zilla::incubator::exporter-stdout.spec + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 1.00 + 0 + + + + + org.kaazing + k3po.lang + provided + + + ${project.groupId} + engine.spec + ${project.version} + + + ${project.groupId} + binding-http.spec + ${project.version} + test + + + ${project.groupId} + binding-proxy.spec + ${project.version} + test + + + junit + junit + test + + + org.kaazing + k3po.junit + test + + + org.hamcrest + hamcrest-library + test + + + + + + + src/main/resources + + + src/main/scripts + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core + io.aklivity.zilla.specs.exporter.stdout.internal.types + + + + + validate + generate + + + + + + com.mycila + license-maven-plugin + + + **/keys + **/trust + **/signers + + + + + maven-checkstyle-plugin + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/specs/exporter/stdout/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + + diff --git a/incubator/exporter-stdout.spec/src/main/moditect/module-info.java b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java new file mode 100644 index 0000000000..9b8473aaae --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/moditect/module-info.java @@ -0,0 +1,18 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +open module io.aklivity.zilla.specs.exporter.stdout +{ + requires transitive io.aklivity.zilla.specs.engine; +} diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml new file mode 100644 index 0000000000..a6daa035f9 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.authorization.credentials.yaml @@ -0,0 +1,53 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +guards: + jwt0: + type: jwt + options: + issuer: https://auth.example.com + audience: https://api.example.com + keys: + - kty: RSA + n: qqEu50hX+43Bx4W1UYWnAVKwFm+vDbP0kuIOSLVNa+HKQdHTf+3Sei5UCnkskn796izA29D0DdCy3ET9oaKRHIJyKbqFl0rv6f516QzOoXKC6N01sXBHBE/ovs0wwDvlaW+gFGPgkzdcfUlyrWLDnLV7LcuQymhTND2uH0oR3wJnNENN/OFgM1KGPPDOe19YsIKdLqARgxrhZVsh06OurEviZTXOBFI5r+yac7haDwOQhLHXNv+Y9MNvxs5QLWPFIM3bNUWfYrJnLrs4hGJS+y/KDM9Si+HL30QAFXy4YNO33J8DHjZ7ddG5n8/FqplOKvRtUgjcKWlxoGY4VdVaDQ== + e: AQAB + alg: RS256 + kid: example +bindings: + net0: + type: http + kind: server + options: + versions: + - http/1.1 + authorization: + jwt0: + credentials: + cookies: + access_token: "{credentials}" + headers: + authorization: Bearer {credentials} + query: + access_token: "{credentials}" + routes: + - exit: app0 + guarded: + jwt0: [] diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml new file mode 100644 index 0000000000..7202d45307 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v1.1/server.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + net0: + type: http + kind: server + options: + versions: + - http/1.1 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml new file mode 100644 index 0000000000..effa3e4a6f --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/config/v2/server.yaml @@ -0,0 +1,33 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + net0: + type: http + kind: server + options: + versions: + - h2 + routes: + - exit: app0 + when: + - headers: + :authority: localhost:8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml new file mode 100644 index 0000000000..ff4c4a3aea --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/client.yaml @@ -0,0 +1,26 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + app0: + type: kafka + kind: client + exit: net0 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml new file mode 100644 index 0000000000..04191ddc52 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tcp/config/client.host.yaml @@ -0,0 +1,28 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +bindings: + app0: + type: tcp + kind: client + options: + host: localhost + port: 8080 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml new file mode 100644 index 0000000000..fd7e4b90a0 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/server.yaml @@ -0,0 +1,38 @@ +# +# Copyright 2021-2023 Aklivity Inc +# +# Licensed under the Aklivity Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# https://www.aklivity.io/aklivity-community-license/ +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +--- +name: test +telemetry: + exporters: + stdout0: + type: stdout +vaults: + server: + type: filesystem + options: + keys: + store: stores/server/keys + type: pkcs12 + password: generated +bindings: + net0: + type: tls + kind: server + vault: server + options: + keys: + - localhost + exit: app0 diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore new file mode 100644 index 0000000000..507484f3e9 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/.gitignore @@ -0,0 +1,2 @@ +*.crt +*.csr diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys new file mode 100644 index 0000000000..d2fa3da9b9 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/keys differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers new file mode 100644 index 0000000000..58e72c4460 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/signers differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust new file mode 100644 index 0000000000..bb624ca8c8 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/client/trust differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys new file mode 100644 index 0000000000..e10b65832b Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/keys differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers new file mode 100644 index 0000000000..0c8e698ab1 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/signers differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust new file mode 100644 index 0000000000..377ed0c4c4 Binary files /dev/null and b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/binding/tls/config/stores/server/trust differ diff --git a/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json new file mode 100644 index 0000000000..f3f7450945 --- /dev/null +++ b/incubator/exporter-stdout.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json @@ -0,0 +1,34 @@ +[ + { + "op": "add", + "path": "/$defs/telemetry/exporter/properties/type/enum/-", + "value": "stdout" + }, + { + "op": "add", + "path": "/$defs/telemetry/exporter/allOf/-", + "value": + { + "if": + { + "properties": + { + "type": + { + "const": "stdout" + } + } + }, + "then": + { + "properties": + { + "type": + { + "const": "stdout" + } + } + } + } + } +] diff --git a/incubator/exporter-stdout/COPYRIGHT b/incubator/exporter-stdout/COPYRIGHT new file mode 100644 index 0000000000..0cb10b6f62 --- /dev/null +++ b/incubator/exporter-stdout/COPYRIGHT @@ -0,0 +1,12 @@ +Copyright ${copyrightYears} Aklivity Inc + +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. diff --git a/incubator/exporter-stdout/LICENSE b/incubator/exporter-stdout/LICENSE new file mode 100644 index 0000000000..f3cf11c3ad --- /dev/null +++ b/incubator/exporter-stdout/LICENSE @@ -0,0 +1,114 @@ + Aklivity Community License Agreement + Version 1.0 + +This Aklivity Community License Agreement Version 1.0 (the “Agreement”) sets +forth the terms on which Aklivity, Inc. (“Aklivity”) makes available certain +software made available by Aklivity under this Agreement (the “Software”). BY +INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF THE SOFTWARE, +YOU AGREE TO THE TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE TO +SUCH TERMS AND CONDITIONS, YOU MUST NOT USE THE SOFTWARE. IF YOU ARE RECEIVING +THE SOFTWARE ON BEHALF OF A LEGAL ENTITY, YOU REPRESENT AND WARRANT THAT YOU +HAVE THE ACTUAL AUTHORITY TO AGREE TO THE TERMS AND CONDITIONS OF THIS +AGREEMENT ON BEHALF OF SUCH ENTITY. “Licensee” means you, an individual, or +the entity on whose behalf you are receiving the Software. + + 1. LICENSE GRANT AND CONDITIONS. + + 1.1 License. Subject to the terms and conditions of this Agreement, + Aklivity hereby grants to Licensee a non-exclusive, royalty-free, + worldwide, non-transferable, non-sublicenseable license during the term + of this Agreement to: (a) use the Software; (b) prepare modifications and + derivative works of the Software; (c) distribute the Software (including + without limitation in source code or object code form); and (d) reproduce + copies of the Software (the “License”). Licensee is not granted the + right to, and Licensee shall not, exercise the License for an Excluded + Purpose. For purposes of this Agreement, “Excluded Purpose” means making + available any software-as-a-service, platform-as-a-service, + infrastructure-as-a-service or other similar online service that competes + with Aklivity products or services that provide the Software. + + 1.2 Conditions. In consideration of the License, Licensee’s distribution + of the Software is subject to the following conditions: + + (a) Licensee must cause any Software modified by Licensee to carry + prominent notices stating that Licensee modified the Software. + + (b) On each Software copy, Licensee shall reproduce and not remove or + alter all Aklivity or third party copyright or other proprietary + notices contained in the Software, and Licensee must provide the + notice below with each copy. + + “This software is made available by Aklivity, Inc., under the + terms of the Aklivity Community License Agreement, Version 1.0 + located at http://www.Aklivity.io/Aklivity-community-license. BY + INSTALLING, DOWNLOADING, ACCESSING, USING OR DISTRIBUTING ANY OF + THE SOFTWARE, YOU AGREE TO THE TERMS OF SUCH LICENSE AGREEMENT.” + + 1.3 Licensee Modifications. Licensee may add its own copyright notices + to modifications made by Licensee and may provide additional or different + license terms and conditions for use, reproduction, or distribution of + Licensee’s modifications. While redistributing the Software or + modifications thereof, Licensee may choose to offer, for a fee or free of + charge, support, warranty, indemnity, or other obligations. Licensee, and + not Aklivity, will be responsible for any such obligations. + + 1.4 No Sublicensing. The License does not include the right to + sublicense the Software, however, each recipient to which Licensee + provides the Software may exercise the Licenses so long as such recipient + agrees to the terms and conditions of this Agreement. + + 2. TERM AND TERMINATION. This Agreement will continue unless and until + earlier terminated as set forth herein. If Licensee breaches any of its + conditions or obligations under this Agreement, this Agreement will + terminate automatically and the License will terminate automatically and + permanently. + + 3. INTELLECTUAL PROPERTY. As between the parties, Aklivity will retain all + right, title, and interest in the Software, and all intellectual property + rights therein. Aklivity hereby reserves all rights not expressly granted + to Licensee in this Agreement. Aklivity hereby reserves all rights in its + trademarks and service marks, and no licenses therein are granted in this + Agreement. + + 4. DISCLAIMER. Aklivity HEREBY DISCLAIMS ANY AND ALL WARRANTIES AND + CONDITIONS, EXPRESS, IMPLIED, STATUTORY, OR OTHERWISE, AND SPECIFICALLY + DISCLAIMS ANY WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR + PURPOSE, WITH RESPECT TO THE SOFTWARE. + + 5. LIMITATION OF LIABILITY. Aklivity WILL NOT BE LIABLE FOR ANY DAMAGES OF + ANY KIND, INCLUDING BUT NOT LIMITED TO, LOST PROFITS OR ANY CONSEQUENTIAL, + SPECIAL, INCIDENTAL, INDIRECT, OR DIRECT DAMAGES, HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, ARISING OUT OF THIS AGREEMENT. THE FOREGOING SHALL + APPLY TO THE EXTENT PERMITTED BY APPLICABLE LAW. + + 6.GENERAL. + + 6.1 Governing Law. This Agreement will be governed by and interpreted in + accordance with the laws of the state of California, without reference to + its conflict of laws principles. If Licensee is located within the + United States, all disputes arising out of this Agreement are subject to + the exclusive jurisdiction of courts located in Santa Clara County, + California. USA. If Licensee is located outside of the United States, + any dispute, controversy or claim arising out of or relating to this + Agreement will be referred to and finally determined by arbitration in + accordance with the JAMS International Arbitration Rules. The tribunal + will consist of one arbitrator. The place of arbitration will be Palo + Alto, California. The language to be used in the arbitral proceedings + will be English. Judgment upon the award rendered by the arbitrator may + be entered in any court having jurisdiction thereof. + + 6.2 Assignment. Licensee is not authorized to assign its rights under + this Agreement to any third party. Aklivity may freely assign its rights + under this Agreement to any third party. + + 6.3 Other. This Agreement is the entire agreement between the parties + regarding the subject matter hereof. No amendment or modification of + this Agreement will be valid or binding upon the parties unless made in + writing and signed by the duly authorized representatives of both + parties. In the event that any provision, including without limitation + any condition, of this Agreement is held to be unenforceable, this + Agreement and all licenses and rights granted hereunder will immediately + terminate. Waiver by Aklivity of a breach of any provision of this + Agreement or the failure by Aklivity to exercise any right hereunder + will not be construed as a waiver of any subsequent breach of that right + or as a waiver of any other right. \ No newline at end of file diff --git a/incubator/exporter-stdout/NOTICE b/incubator/exporter-stdout/NOTICE new file mode 100644 index 0000000000..9024d8926d --- /dev/null +++ b/incubator/exporter-stdout/NOTICE @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: + diff --git a/incubator/exporter-stdout/NOTICE.template b/incubator/exporter-stdout/NOTICE.template new file mode 100644 index 0000000000..209ca12f74 --- /dev/null +++ b/incubator/exporter-stdout/NOTICE.template @@ -0,0 +1,13 @@ +Licensed under the Aklivity Community License (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at + + https://www.aklivity.io/aklivity-community-license/ + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +This project includes: +#GENERATED_NOTICES# diff --git a/incubator/exporter-stdout/mvnw b/incubator/exporter-stdout/mvnw new file mode 100755 index 0000000000..d2f0ea3808 --- /dev/null +++ b/incubator/exporter-stdout/mvnw @@ -0,0 +1,310 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you 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 +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # See https://developer.apple.com/library/mac/qa/qa1170/_index.html + if [ -z "$JAVA_HOME" ]; then + if [ -x "/usr/libexec/java_home" ]; then + export JAVA_HOME="`/usr/libexec/java_home`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Mingw, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + + if [ -z "$1" ] + then + echo "Path not specified to find_maven_basedir" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +########################################################################################## +# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +# This allows using the maven wrapper in projects that prohibit checking in binary data. +########################################################################################## +if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found .mvn/wrapper/maven-wrapper.jar" + fi +else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." + fi + if [ -n "$MVNW_REPOURL" ]; then + jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + else + jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + fi + while IFS="=" read key value; do + case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + esac + done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" + if [ "$MVNW_VERBOSE" = true ]; then + echo "Downloading from: $jarUrl" + fi + wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + if $cygwin; then + wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + fi + + if command -v wget > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found wget ... using wget" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + wget "$jarUrl" -O "$wrapperJarPath" + else + wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" + fi + elif command -v curl > /dev/null; then + if [ "$MVNW_VERBOSE" = true ]; then + echo "Found curl ... using curl" + fi + if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then + curl -o "$wrapperJarPath" "$jarUrl" -f + else + curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + fi + + else + if [ "$MVNW_VERBOSE" = true ]; then + echo "Falling back to using Java to download" + fi + javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + # For Cygwin, switch paths to Windows format before running javac + if $cygwin; then + javaClass=`cygpath --path --windows "$javaClass"` + fi + if [ -e "$javaClass" ]; then + if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Compiling MavenWrapperDownloader.java ..." + fi + # Compiling the Java class + ("$JAVA_HOME/bin/javac" "$javaClass") + fi + if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then + # Running the downloader + if [ "$MVNW_VERBOSE" = true ]; then + echo " - Running MavenWrapperDownloader.java ..." + fi + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + fi + fi + fi +fi +########################################################################################## +# End of extension +########################################################################################## + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +if [ "$MVNW_VERBOSE" = true ]; then + echo $MAVEN_PROJECTBASEDIR +fi +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/incubator/exporter-stdout/mvnw.cmd b/incubator/exporter-stdout/mvnw.cmd new file mode 100644 index 0000000000..b26ab24f03 --- /dev/null +++ b/incubator/exporter-stdout/mvnw.cmd @@ -0,0 +1,182 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM set title of command window +title %0 +@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" +set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + +FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B +) + +@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central +@REM This allows using the maven wrapper in projects that prohibit checking in binary data. +if exist %WRAPPER_JAR% ( + if "%MVNW_VERBOSE%" == "true" ( + echo Found %WRAPPER_JAR% + ) +) else ( + if not "%MVNW_REPOURL%" == "" ( + SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar" + ) + if "%MVNW_VERBOSE%" == "true" ( + echo Couldn't find %WRAPPER_JAR%, downloading it ... + echo Downloading from: %DOWNLOAD_URL% + ) + + powershell -Command "&{"^ + "$webclient = new-object System.Net.WebClient;"^ + "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ + "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ + "}"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "}" + if "%MVNW_VERBOSE%" == "true" ( + echo Finished downloading %WRAPPER_JAR% + ) +) +@REM End of extension + +@REM Provide a "standardized" way to retrieve the CLI args that will +@REM work with both Windows and non-Windows executions. +set MAVEN_CMD_LINE_ARGS=%* + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/incubator/exporter-stdout/pom.xml b/incubator/exporter-stdout/pom.xml new file mode 100644 index 0000000000..6621d1ce6a --- /dev/null +++ b/incubator/exporter-stdout/pom.xml @@ -0,0 +1,340 @@ + + + + 4.0.0 + + io.aklivity.zilla + incubator + 0.9.69 + ../pom.xml + + + exporter-stdout + zilla::incubator::exporter-stdout + + + + Aklivity Community License Agreement + https://www.aklivity.io/aklivity-community-license/ + repo + + + + + 11 + 11 + 0.75 + 0 + + + + + ${project.groupId} + exporter-stdout.spec + ${project.version} + provided + + + ${project.groupId} + engine + ${project.version} + provided + + + ${project.groupId} + binding-http.spec + ${project.version} + provided + + + ${project.groupId} + binding-kafka.spec + ${project.version} + provided + + + ${project.groupId} + binding-tcp.spec + ${project.version} + provided + + + ${project.groupId} + binding-tls.spec + ${project.version} + provided + + + ${project.groupId} + catalog-schema-registry.spec + ${project.version} + provided + + + ${project.groupId} + guard-jwt.spec + ${project.version} + provided + + + ${project.groupId} + vault-filesystem.spec + ${project.version} + provided + + + ${project.groupId} + engine + test-jar + ${project.version} + test + + + ${project.groupId} + binding-http + ${project.version} + test + + + ${project.groupId} + binding-kafka + ${project.version} + test + + + ${project.groupId} + binding-tcp + ${project.version} + test + + + ${project.groupId} + binding-tls + ${project.version} + test + + + ${project.groupId} + catalog-schema-registry + ${project.version} + test + + + ${project.groupId} + guard-jwt + ${project.version} + test + + + ${project.groupId} + model-json + ${project.version} + test + + + ${project.groupId} + vault-filesystem + ${project.version} + test + + + junit + junit + test + + + org.hamcrest + hamcrest + test + + + com.vtence.hamcrest + hamcrest-jpa + test + + + com.github.npathai + hamcrest-optional + test + + + org.mockito + mockito-core + test + + + org.kaazing + k3po.junit + test + + + org.kaazing + k3po.lang + test + + + org.openjdk.jmh + jmh-core + test + + + org.openjdk.jmh + jmh-generator-annprocess + test + + + + + + + org.jasig.maven + maven-notice-plugin + + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core http jwt kafka schema_registry tcp tls + io.aklivity.zilla.runtime.exporter.stdout.internal.types + + + + + generate + + + + + + com.mycila + license-maven-plugin + + + **/org.mockito.plugins.MockMaker + + + + + maven-checkstyle-plugin + + + maven-dependency-plugin + + + process-resources + + unpack + + + + + ${project.groupId} + exporter-stdout.spec + + + ^\Qio/aklivity/zilla/specs/exporter/stdout/\E + io/aklivity/zilla/runtime/exporter/stdout/internal/ + + + + + io/aklivity/zilla/specs/exporter/stdout/schema/stdout.schema.patch.json + ${project.build.directory}/classes + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-surefire-plugin + + + org.moditect + moditect-maven-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.kaazing + k3po-maven-plugin + + + ${project.groupId} + engine + ${project.version} + test-jar + + + ${project.groupId} + engine + ${project.version} + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + org.jacoco + jacoco-maven-plugin + + + io/aklivity/zilla/runtime/exporter/stdout/internal/types/**/*.class + + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.coverage.ratio} + + + CLASS + MISSEDCOUNT + ${jacoco.missed.count} + + + + + + + + io.gatling + maven-shade-plugin + + + + org.agrona:agrona + io.aklivity.zilla:engine + org.openjdk.jmh:jmh-core + net.sf.jopt-simple:jopt-simple + org.apache.commons:commons-math3 + commons-cli:commons-cli + com.github.biboudis:jmh-profilers + + + + + + + diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java new file mode 100644 index 0000000000..bbc9cfb038 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutConfiguration.java @@ -0,0 +1,67 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.io.PrintStream; +import java.lang.reflect.Field; + +import org.agrona.LangUtil; + +import io.aklivity.zilla.runtime.engine.Configuration; + +public class StdoutConfiguration extends Configuration +{ + private static final ConfigurationDef STDOUT_CONFIG; + + public static final PropertyDef STDOUT_OUTPUT; + + static + { + final ConfigurationDef config = new ConfigurationDef("zilla.exporter.stdout"); + STDOUT_OUTPUT = config.property(PrintStream.class, "output", + StdoutConfiguration::decodeOutput, c -> System.out); + STDOUT_CONFIG = config; + } + + public StdoutConfiguration( + Configuration config) + { + super(STDOUT_CONFIG, config); + } + + public PrintStream output() + { + return STDOUT_OUTPUT.get(this); + } + + private static PrintStream decodeOutput( + Configuration config, + String value) + { + try + { + int fieldAt = value.lastIndexOf("."); + Class ownerClass = Class.forName(value.substring(0, fieldAt)); + String fieldName = value.substring(fieldAt + 1); + Field field = ownerClass.getDeclaredField(fieldName); + return (PrintStream) field.get(null); + } + catch (Throwable ex) + { + LangUtil.rethrowUnchecked(ex); + } + return null; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java new file mode 100644 index 0000000000..3e606c2d64 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporter.java @@ -0,0 +1,53 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.net.URL; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; + +public class StdoutExporter implements Exporter +{ + public static final String NAME = "stdout"; + + private final StdoutConfiguration config; + + public StdoutExporter( + StdoutConfiguration config) + { + this.config = config; + } + + @Override + public String name() + { + return NAME; + } + + @Override + public URL type() + { + return getClass().getResource("schema/stdout.schema.patch.json"); + } + + @Override + public ExporterContext supply( + EngineContext context) + { + return new StdoutExporterContext(config, context); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java new file mode 100644 index 0000000000..bb8d7d3adb --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterContext.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.util.List; +import java.util.function.LongFunction; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; +import io.aklivity.zilla.runtime.engine.config.AttributeConfig; +import io.aklivity.zilla.runtime.engine.config.ExporterConfig; +import io.aklivity.zilla.runtime.engine.config.KindConfig; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler; +import io.aklivity.zilla.runtime.engine.metrics.Collector; +import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig; + +public class StdoutExporterContext implements ExporterContext +{ + private final StdoutConfiguration config; + private final EngineContext context; + + public StdoutExporterContext( + StdoutConfiguration config, + EngineContext context) + { + this.config = config; + this.context = context; + } + + @Override + public ExporterHandler attach( + ExporterConfig exporter, + List attributes, + Collector collector, + LongFunction resolveKind) + { + StdoutExporterConfig stdoutExporter = new StdoutExporterConfig(exporter); + return new StdoutExporterHandler(config, context, stdoutExporter); + } + + @Override + public void detach( + long exporterId) + { + } + + public String supplyQName( + long namespacedId) + { + return context.supplyQName(namespacedId); + } + + public int supplyTypeId( + String name) + { + return context.supplyTypeId(name); + } + + public MessageReader supplyEventReader() + { + return context.supplyEventReader(); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java new file mode 100644 index 0000000000..16b7a2e20f --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactorySpi.java @@ -0,0 +1,37 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import io.aklivity.zilla.runtime.common.feature.Incubating; +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi; + +@Incubating +public class StdoutExporterFactorySpi implements ExporterFactorySpi +{ + @Override + public String type() + { + return StdoutExporter.NAME; + } + + @Override + public Exporter create( + Configuration config) + { + return new StdoutExporter(new StdoutConfiguration(config)); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java new file mode 100644 index 0000000000..98a0ed322e --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import java.io.PrintStream; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterHandler; +import io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutExporterConfig; +import io.aklivity.zilla.runtime.exporter.stdout.internal.stream.StdoutEventsStream; + +public class StdoutExporterHandler implements ExporterHandler +{ + private final StdoutExporterContext context; + private final PrintStream out; + + private StdoutEventsStream events; + + public StdoutExporterHandler( + StdoutConfiguration config, + EngineContext context, + StdoutExporterConfig exporter) + { + this.context = new StdoutExporterContext(config, context); + this.out = config.output(); + } + + @Override + public void start() + { + events = new StdoutEventsStream(context, out); + } + + @Override + public int export() + { + return events.process(); + } + + @Override + public void stop() + { + this.events = null; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java new file mode 100644 index 0000000000..8f9f7ca38a --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutExporterConfig.java @@ -0,0 +1,33 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import io.aklivity.zilla.runtime.engine.config.ExporterConfig; + +public class StdoutExporterConfig +{ + private final StdoutOptionsConfig options; + + public StdoutExporterConfig( + ExporterConfig exporter) + { + this.options = (StdoutOptionsConfig)exporter.options; + } + + public StdoutOptionsConfig options() + { + return options; + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java new file mode 100644 index 0000000000..43f11bb071 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfig.java @@ -0,0 +1,21 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public class StdoutOptionsConfig extends OptionsConfig +{ +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java new file mode 100644 index 0000000000..fb2bbf8068 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import static io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi.Kind.EXPORTER; + +import jakarta.json.Json; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.bind.adapter.JsonbAdapter; + +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporter; + +public class StdoutOptionsConfigAdapter implements OptionsConfigAdapterSpi, JsonbAdapter +{ + @Override + public Kind kind() + { + return EXPORTER; + } + + @Override + public String type() + { + return StdoutExporter.NAME; + } + + @Override + public JsonObject adaptToJson( + OptionsConfig options) + { + JsonObjectBuilder object = Json.createObjectBuilder(); + return object.build(); + } + + @Override + public OptionsConfig adaptFromJson( + JsonObject object) + { + return new StdoutOptionsConfig(); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java new file mode 100644 index 0000000000..f8f2a84e8b --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/EventHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.StringFW; + +public abstract class EventHandler +{ + protected static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MMM/yyyy:HH:mm:ss Z"); + + protected final StdoutExporterContext context; + protected final PrintStream out; + + public EventHandler( + StdoutExporterContext context, + PrintStream out) + { + this.context = context; + this.out = out; + } + + public abstract void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length); + + protected static String asString( + StringFW stringFW) + { + String s = stringFW.asString(); + return s == null ? "" : s; + } + + protected static String identity( + StringFW identity) + { + int length = identity.length(); + return length <= 0 ? "-" : identity.asString(); + } + + protected static String asDateTime( + long timestamp) + { + Instant instant = Instant.ofEpochMilli(timestamp); + OffsetDateTime offsetDateTime = OffsetDateTime.ofInstant(instant, ZoneId.systemDefault()); + return offsetDateTime.format(FORMATTER); + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java new file mode 100644 index 0000000000..c6e330f377 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutEventsStream.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; +import org.agrona.collections.Int2ObjectHashMap; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; + +public class StdoutEventsStream +{ + private final MessageReader readEvent; + private final Int2ObjectHashMap eventHandlers; + + public StdoutEventsStream( + StdoutExporterContext context, + PrintStream out) + { + this.readEvent = context.supplyEventReader(); + + final Int2ObjectHashMap eventHandlers = new Int2ObjectHashMap<>(); + eventHandlers.put(context.supplyTypeId("http"), new StdoutHttpHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("jwt"), new StdoutJwtHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("kafka"), new StdoutKafkaHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("schema-registry"), + new StdoutSchemaRegistryHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("tcp"), new StdoutTcpHandler(context, out)::handleEvent); + eventHandlers.put(context.supplyTypeId("tls"), new StdoutTlsHandler(context, out)::handleEvent); + this.eventHandlers = eventHandlers; + } + + public int process() + { + return readEvent.read(this::handleEvent, 1); + } + + private void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final MessageConsumer handler = eventHandlers.get(msgTypeId); + if (handler != null) + { + handler.accept(msgTypeId, buffer, index, length); + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java new file mode 100644 index 0000000000..86dabe799b --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutHttpHandler.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpEventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.HttpRequestAcceptedFW; + +public class StdoutHttpHandler extends EventHandler +{ + private static final String REQUEST_ACCEPTED_FORMAT = "%s %s [%s] REQUEST_ACCEPTED %s %s %s %s%n"; + + private final HttpEventFW httpEventRO = new HttpEventFW(); + + public StdoutHttpHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final HttpEventFW event = httpEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case REQUEST_ACCEPTED: + { + HttpRequestAcceptedFW e = event.requestAccepted(); + String qname = context.supplyQName(e.namespacedId()); + out.format(REQUEST_ACCEPTED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp()), asString(e.scheme()), + asString(e.method()), asString(e.authority()), asString(e.path())); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java new file mode 100644 index 0000000000..7acf1ee0fe --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutJwtHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtAuthorizationFailedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.JwtEventFW; + +public class StdoutJwtHandler extends EventHandler +{ + private static final String AUTHORIZATION_FAILED_FORMAT = "%s %s [%s] AUTHORIZATION_FAILED%n"; + + private final JwtEventFW jwtEventRO = new JwtEventFW(); + + public StdoutJwtHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + JwtEventFW event = jwtEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case AUTHORIZATION_FAILED: + JwtAuthorizationFailedFW e = event.authorizationFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(AUTHORIZATION_FAILED_FORMAT, qname, identity(e.identity()), asDateTime(e.timestamp())); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java new file mode 100644 index 0000000000..bae2026051 --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutKafkaHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaApiVersionRejectedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.KafkaEventFW; + +public class StdoutKafkaHandler extends EventHandler +{ + private static final String AUTHORIZATION_FAILED_FORMAT = "%s - [%s] AUTHORIZATION_FAILED%n"; + private static final String API_VERSION_REJECTED_FORMAT = "%s - [%s] API_VERSION_REJECTED %d %d%n"; + + private final KafkaEventFW kafkaEventRO = new KafkaEventFW(); + + public StdoutKafkaHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final KafkaEventFW event = kafkaEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case AUTHORIZATION_FAILED: + { + EventFW e = event.authorizationFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(AUTHORIZATION_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case API_VERSION_REJECTED: + { + KafkaApiVersionRejectedFW e = event.apiVersionRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(API_VERSION_REJECTED_FORMAT, qname, asDateTime(e.timestamp()), e.apiKey(), e.apiVersion()); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java new file mode 100644 index 0000000000..376826b0fd --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutSchemaRegistryHandler.java @@ -0,0 +1,55 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryEventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.SchemaRegistryRemoteAccessRejectedFW; + +public class StdoutSchemaRegistryHandler extends EventHandler +{ + private static final String REMOTE_ACCESS_REJECTED = "%s - [%s] REMOTE_ACCESS_REJECTED %s %s %d%n"; + + private final SchemaRegistryEventFW schemaRegistryEventRO = new SchemaRegistryEventFW(); + + public StdoutSchemaRegistryHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + SchemaRegistryEventFW event = schemaRegistryEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case REMOTE_ACCESS_REJECTED: + SchemaRegistryRemoteAccessRejectedFW e = event.remoteAccessRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(REMOTE_ACCESS_REJECTED, qname, asDateTime(e.timestamp()), asString(e.method()), asString(e.url()), + e.status()); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java new file mode 100644 index 0000000000..0584a4eddf --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTcpHandler.java @@ -0,0 +1,54 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpDnsFailedFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TcpEventFW; + +public class StdoutTcpHandler extends EventHandler +{ + private static final String DNS_FAILED_FORMAT = "%s - [%s] DNS_FAILED %s%n"; + + private final TcpEventFW tcpEventRO = new TcpEventFW(); + + public StdoutTcpHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + final TcpEventFW event = tcpEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case DNS_FAILED: + TcpDnsFailedFW e = event.dnsFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(DNS_FAILED_FORMAT, qname, asDateTime(e.timestamp()), asString(e.address())); + break; + } + } +} diff --git a/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java new file mode 100644 index 0000000000..a3ecce6abf --- /dev/null +++ b/incubator/exporter-stdout/src/main/java/io/aklivity/zilla/runtime/exporter/stdout/internal/stream/StdoutTlsHandler.java @@ -0,0 +1,88 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.stream; + +import java.io.PrintStream; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterContext; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.EventFW; +import io.aklivity.zilla.runtime.exporter.stdout.internal.types.event.TlsEventFW; + +public class StdoutTlsHandler extends EventHandler +{ + private static final String TLS_FAILED_FORMAT = "%s - [%s] TLS_FAILED%n"; + private static final String PROTOCOL_REJECTED_FORMAT = "%s - [%s] PROTOCOL_REJECTED%n"; + private static final String KEY_REJECTED_FORMAT = "%s - [%s] KEY_REJECTED%n"; + private static final String PEER_NOT_VERIFIED_FORMAT = "%s - [%s] PEER_NOT_VERIFIED%n"; + private static final String HANDSHAKE_FAILED_FORMAT = "%s - [%s] HANDSHAKE_FAILED%n"; + + private final TlsEventFW tlsEventRO = new TlsEventFW(); + + public StdoutTlsHandler( + StdoutExporterContext context, + PrintStream out) + { + super(context, out); + } + + public void handleEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + TlsEventFW event = tlsEventRO.wrap(buffer, index, index + length); + switch (event.kind()) + { + case TLS_FAILED: + { + EventFW e = event.tlsFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(TLS_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_PROTOCOL_REJECTED: + { + EventFW e = event.tlsProtocolRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(PROTOCOL_REJECTED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_KEY_REJECTED: + { + EventFW e = event.tlsKeyRejected(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(KEY_REJECTED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_PEER_NOT_VERIFIED: + { + EventFW e = event.tlsPeerNotVerified(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(PEER_NOT_VERIFIED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + case TLS_HANDSHAKE_FAILED: + { + EventFW e = event.tlsHandshakeFailed(); + String qname = context.supplyQName(e.namespacedId()); + out.printf(HANDSHAKE_FAILED_FORMAT, qname, asDateTime(e.timestamp())); + break; + } + } + } +} diff --git a/incubator/exporter-stdout/src/main/moditect/module-info.java b/incubator/exporter-stdout/src/main/moditect/module-info.java new file mode 100644 index 0000000000..c89775c1c8 --- /dev/null +++ b/incubator/exporter-stdout/src/main/moditect/module-info.java @@ -0,0 +1,24 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +module io.aklivity.zilla.runtime.exporter.stdout +{ + requires io.aklivity.zilla.runtime.engine; + + provides io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi + with io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi; + + provides io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi + with io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter; +} diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi new file mode 100644 index 0000000000..c0bfc37413 --- /dev/null +++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.exporter.stdout.internal.config.StdoutOptionsConfigAdapter \ No newline at end of file diff --git a/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi new file mode 100644 index 0000000000..8f575d444b --- /dev/null +++ b/incubator/exporter-stdout/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.exporter.ExporterFactorySpi @@ -0,0 +1 @@ +io.aklivity.zilla.runtime.exporter.stdout.internal.StdoutExporterFactorySpi \ No newline at end of file diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java new file mode 100644 index 0000000000..3389e46b3a --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/StdoutExporterFactoryTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.mock; + +import java.net.URL; + +import org.junit.Test; + +import io.aklivity.zilla.runtime.engine.Configuration; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.exporter.Exporter; +import io.aklivity.zilla.runtime.engine.exporter.ExporterContext; +import io.aklivity.zilla.runtime.engine.exporter.ExporterFactory; + +public final class StdoutExporterFactoryTest +{ + @Test + public void shouldLoadAndCreate() + { + // GIVEN + Configuration config = new Configuration(); + ExporterFactory factory = ExporterFactory.instantiate(); + + // WHEN + Exporter exporter = factory.create("stdout", config); + ExporterContext context = exporter.supply(mock(EngineContext.class)); + + // THEN + assertThat(exporter, instanceOf(StdoutExporter.class)); + assertThat(exporter.name(), equalTo("stdout")); + assertThat(exporter.type(), instanceOf(URL.class)); + assertThat(context, instanceOf(ExporterContext.class)); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java new file mode 100644 index 0000000000..a0fdab3dbe --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/config/StdoutOptionsConfigAdapterTest.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.config; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +import jakarta.json.bind.Jsonb; +import jakarta.json.bind.JsonbBuilder; +import jakarta.json.bind.JsonbConfig; + +import org.junit.Before; +import org.junit.Test; + +public class StdoutOptionsConfigAdapterTest +{ + private Jsonb jsonb; + + @Before + public void initJson() + { + JsonbConfig config = new JsonbConfig() + .withAdapters(new StdoutOptionsConfigAdapter()); + jsonb = JsonbBuilder.create(config); + } + + @Test + public void shouldReadOptions() + { + // GIVEN + String text = "{}"; + + // WHEN + StdoutOptionsConfig options = jsonb.fromJson(text, StdoutOptionsConfig.class); + + // THEN + assertThat(options, not(nullValue())); + } + + @Test + public void shouldWriteOptions() + { + // GIVEN + String expectedText = "{}"; + StdoutOptionsConfig config = new StdoutOptionsConfig(); + + // WHEN + String text = jsonb.toJson(config); + + // THEN + assertThat(text, not(nullValue())); + assertThat(text, equalTo(expectedText)); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java new file mode 100644 index 0000000000..c942d6e141 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http11EventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_BUFFER_SLOT_CAPACITY; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class Http11EventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/message.format") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/message.format"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(ENGINE_BUFFER_SLOT_CAPACITY, 8192) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/request.with.headers/client", + "${app}/request.with.headers/server" }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void requestWithHeaders() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java new file mode 100644 index 0000000000..e858786c85 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/Http2EventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_CONCURRENT_STREAMS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class Http2EventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7540/message.format") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7540/message.format"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(HTTP_CONCURRENT_STREAMS, 100) + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v2") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/connection.headers/client", + "${app}/connection.headers/server" }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void connectionHeaders() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] REQUEST_ACCEPTED http GET localhost:8080 /\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java new file mode 100644 index 0000000000..41fa36d119 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/JwtEventsIT.java @@ -0,0 +1,68 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration.HTTP_SERVER_HEADER; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class JwtEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/http/streams/application/rfc7230/authorization"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure(HTTP_SERVER_HEADER, "Zilla") + .configurationRoot("io/aklivity/zilla/specs/binding/http/config/v1.1") + .external("app0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.authorization.credentials.yaml") + @Specification({ + "${net}/reject.credentials.header/client", + }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldRejectCredentialsHeader() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 user \\[[^\\]]+\\] AUTHORIZATION_FAILED\n")); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java new file mode 100644 index 0000000000..e1e8c9fda7 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/KafkaEventsIT.java @@ -0,0 +1,73 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class KafkaEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/kafka/streams/network/group.f1.j5.s3.l3.h3") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/kafka/streams/application/group"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(15, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configure("zilla.binding.kafka.client.instance.id", + "io.aklivity.zilla.runtime.exporter.stdout.internal.events.KafkaEventsIT::supplyInstanceId") + .configurationRoot("io/aklivity/zilla/specs/binding/kafka/config") + .external("net0") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.yaml") + @Specification({ + "${app}/invalid.describe.config/client", + "${net}/invalid.describe.config/server"}) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldHandleInvalidDescribeConfig() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] API_VERSION_REJECTED 32 0\n")); + } + + public static String supplyInstanceId() + { + return "zilla"; + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java new file mode 100644 index 0000000000..be79fd3ba8 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/StdoutOutputRule.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.matchesPattern; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.regex.Pattern; + +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +public final class StdoutOutputRule implements TestRule +{ + public static final PrintStream OUT; + + private static final ByteArrayOutputStream BOS; + + static + { + BOS = new ByteArrayOutputStream(); + OUT = new PrintStream(BOS); + } + + private Pattern expected; + + @Override + public Statement apply( + Statement base, + Description description) + { + return new Statement() + { + @Override + public void evaluate() throws Throwable + { + BOS.reset(); + base.evaluate(); + assertThat(BOS.toString(StandardCharsets.UTF_8), matchesPattern(expected)); + } + }; + } + + public void expect( + Pattern expected) + { + this.expected = expected; + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java new file mode 100644 index 0000000000..989f89eb83 --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TcpEventsIT.java @@ -0,0 +1,75 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class TcpEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tcp/streams/network/rfc793") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tcp/streams/application/rfc793"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(5, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(4096) + .configurationRoot("io/aklivity/zilla/specs/binding/tcp/config") + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("client.host.yaml") + @Specification({ + "${app}/connection.failed/client" + }) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + @Configure(name = "zilla.engine.host.resolver", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.TcpEventsIT::resolveHost") + public void dnsResolutionFailed() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.app0 - \\[[^\\]]+\\] DNS_FAILED localhost\n")); + } + + public static InetAddress[] resolveHost( + String host) throws UnknownHostException + { + throw new UnknownHostException(); + } +} diff --git a/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java new file mode 100644 index 0000000000..cb201e110c --- /dev/null +++ b/incubator/exporter-stdout/src/test/java/io/aklivity/zilla/runtime/exporter/stdout/internal/events/TlsEventsIT.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.exporter.stdout.internal.events; + +import static io.aklivity.zilla.runtime.engine.EngineConfiguration.ENGINE_DRAIN_ON_CLOSE; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.junit.rules.RuleChain.outerRule; + +import java.util.regex.Pattern; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.kaazing.k3po.junit.annotation.Specification; +import org.kaazing.k3po.junit.rules.K3poRule; + +import io.aklivity.zilla.runtime.engine.test.EngineRule; +import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; + +public class TlsEventsIT +{ + private final K3poRule k3po = new K3poRule() + .addScriptRoot("net", "io/aklivity/zilla/specs/binding/tls/streams/network") + .addScriptRoot("app", "io/aklivity/zilla/specs/binding/tls/streams/application"); + + private final TestRule timeout = new DisableOnDebug(new Timeout(10, SECONDS)); + + private final EngineRule engine = new EngineRule() + .directory("target/zilla-itests") + .countersBufferCapacity(8192) + .configurationRoot("io/aklivity/zilla/specs/binding/tls/config") + .external("app0") + .configure(ENGINE_DRAIN_ON_CLOSE, false) + .clean(); + + private final StdoutOutputRule output = new StdoutOutputRule(); + + @Rule + public final TestRule chain = outerRule(output).around(engine).around(k3po).around(timeout); + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/client.hello.malformed/client"}) + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldResetMalformedClientHello() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] TLS_FAILED\n")); + } + + @Test + @Configuration("server.yaml") + @Specification({ + "${net}/server.handshake.timeout/client"}) + @Configure(name = "zilla.binding.tls.handshake.timeout", value = "1") + @Configure(name = "zilla.exporter.stdout.output", + value = "io.aklivity.zilla.runtime.exporter.stdout.internal.events.StdoutOutputRule.OUT") + public void shouldTimeoutHandshake() throws Exception + { + k3po.finish(); + output.expect(Pattern.compile("test.net0 - \\[[^\\]]+\\] HANDSHAKE_FAILED\n")); + } +} diff --git a/incubator/model-avro.spec/pom.xml b/incubator/model-avro.spec/pom.xml index 97464fc311..f9321934dc 100644 --- a/incubator/model-avro.spec/pom.xml +++ b/incubator/model-avro.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json b/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json index 7d1a7c526b..0d2cc8dbe6 100644 --- a/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json +++ b/incubator/model-avro.spec/src/main/scripts/io/aklivity/zilla/specs/model/avro/schema/avro.schema.patch.json @@ -129,6 +129,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } diff --git a/incubator/model-avro/pom.xml b/incubator/model-avro/pom.xml index a9143ccf2c..4def40572a 100644 --- a/incubator/model-avro/pom.xml +++ b/incubator/model-avro/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-core.spec/pom.xml b/incubator/model-core.spec/pom.xml index ac96f3daca..f63fcc5c0b 100644 --- a/incubator/model-core.spec/pom.xml +++ b/incubator/model-core.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-core/pom.xml b/incubator/model-core/pom.xml index a771264c4b..9c9d5a9f68 100644 --- a/incubator/model-core/pom.xml +++ b/incubator/model-core/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-json.spec/pom.xml b/incubator/model-json.spec/pom.xml index f3df26494b..06ed66555d 100644 --- a/incubator/model-json.spec/pom.xml +++ b/incubator/model-json.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json b/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json index b9469bc6dc..11bb3f95b3 100644 --- a/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json +++ b/incubator/model-json.spec/src/main/scripts/io/aklivity/zilla/specs/model/json/schema/json.schema.patch.json @@ -121,6 +121,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } @@ -247,6 +251,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } diff --git a/incubator/model-json/pom.xml b/incubator/model-json/pom.xml index 75c4cc2838..686ed0b5b0 100644 --- a/incubator/model-json/pom.xml +++ b/incubator/model-json/pom.xml @@ -6,7 +6,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java index e86b7dfb3c..5b425d2bae 100644 --- a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java +++ b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonModelHandler.java @@ -72,17 +72,16 @@ protected final boolean validate( int index, int length) { - boolean status = false; + boolean status = true; try { JsonProvider provider = supplyProvider(schemaId); in.wrap(buffer, index, length); provider.createReader(in).readValue(); - status = true; } catch (JsonValidatingException ex) { - ex.printStackTrace(); + status = false; } return status; } diff --git a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java index ad62353542..fa78257ad6 100644 --- a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java +++ b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonReadConverterHandler.java @@ -65,11 +65,13 @@ private int decodePayload( } } - if (validate(schemaId, data, index, length)) + if (schemaId != NO_SCHEMA_ID && + validate(schemaId, data, index, length)) { next.accept(data, index, length); valLength = length; } + return valLength; } } diff --git a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java index 994601abc4..445d88193e 100644 --- a/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java +++ b/incubator/model-json/src/main/java/io/aklivity/zilla/runtime/model/json/internal/JsonValidatorHandler.java @@ -85,7 +85,6 @@ public boolean validate( catch (JsonParsingException ex) { status = false; - ex.printStackTrace(); } return status; diff --git a/incubator/model-protobuf.spec/pom.xml b/incubator/model-protobuf.spec/pom.xml index d86a844d2b..db634b1928 100644 --- a/incubator/model-protobuf.spec/pom.xml +++ b/incubator/model-protobuf.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json b/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json index 578800f5d2..b47474ff73 100644 --- a/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json +++ b/incubator/model-protobuf.spec/src/main/scripts/io/aklivity/zilla/specs/model/protobuf/schema/protobuf.schema.patch.json @@ -62,7 +62,8 @@ }, "required": [ - "id" + "id", + "record" ], "additionalProperties": false }, @@ -86,7 +87,8 @@ }, "required": [ - "schema" + "schema", + "record" ], "additionalProperties": false }, @@ -110,7 +112,8 @@ }, "required": [ - "strategy" + "strategy", + "record" ], "additionalProperties": false }, @@ -134,7 +137,8 @@ }, "required": [ - "subject" + "subject", + "record" ], "additionalProperties": false } @@ -145,6 +149,10 @@ "maxProperties": 1 } }, + "required": + [ + "catalog" + ], "additionalProperties": false } } diff --git a/incubator/model-protobuf/pom.xml b/incubator/model-protobuf/pom.xml index f99855b58d..38c7e07ce4 100644 --- a/incubator/model-protobuf/pom.xml +++ b/incubator/model-protobuf/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla incubator - 0.9.68 + 0.9.69 ../pom.xml diff --git a/incubator/pom.xml b/incubator/pom.xml index 6ce369ad30..ee34ff9d53 100644 --- a/incubator/pom.xml +++ b/incubator/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml @@ -18,15 +18,22 @@ binding-amqp.spec + binding-asyncapi.spec + binding-openapi.spec + binding-openapi-asyncapi.spec catalog-inline.spec catalog-schema-registry.spec exporter-otlp.spec + exporter-stdout.spec model-avro.spec model-core.spec model-json.spec model-protobuf.spec binding-amqp + binding-asyncapi + binding-openapi + binding-openapi-asyncapi catalog-inline catalog-schema-registry @@ -37,6 +44,7 @@ command-tune exporter-otlp + exporter-stdout model-avro model-core @@ -51,6 +59,21 @@ binding-amqp ${project.version} + + ${project.groupId} + binding-asyncapi + ${project.version} + + + ${project.groupId} + binding-openapi + ${project.version} + + + ${project.groupId} + binding-openapi-asyncapi + ${project.version} + ${project.groupId} catalog-inline @@ -86,6 +109,11 @@ exporter-otlp ${project.version} + + ${project.groupId} + exporter-stdout + ${project.version} + ${project.groupId} model-avro diff --git a/manager/pom.xml b/manager/pom.xml index 9995311a87..55c2a71217 100644 --- a/manager/pom.xml +++ b/manager/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml diff --git a/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java b/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java index fcba561d66..9278bfc1d7 100644 --- a/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java +++ b/manager/src/main/java/io/aklivity/zilla/manager/internal/commands/install/ZpmInstall.java @@ -704,6 +704,7 @@ private void generateLauncher() throws IOException Path zillaPath = launcherDir.resolve("zilla"); Files.write(zillaPath, Arrays.asList( "#!/bin/sh", + "JAVA_OPTIONS+=\"${ZILLA_INCUBATOR_ENABLED:+ -Dzilla.incubator.enabled=$ZILLA_INCUBATOR_ENABLED}\"", "cd \"${0%/*}\"", String.format(String.join(" ", Arrays.asList( "exec %s/bin/java", diff --git a/pom.xml b/pom.xml index e1d9f3d069..a3fe52c36c 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ 4.0.0 io.aklivity.zilla zilla - 0.9.68 + 0.9.69 pom zilla https://github.com/aklivity/zilla diff --git a/runtime/binding-echo/pom.xml b/runtime/binding-echo/pom.xml index 58ca2bfd4f..461b9066d7 100644 --- a/runtime/binding-echo/pom.xml +++ b/runtime/binding-echo/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-fan/pom.xml b/runtime/binding-fan/pom.xml index e6012d161c..799039a1d7 100644 --- a/runtime/binding-fan/pom.xml +++ b/runtime/binding-fan/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-filesystem/pom.xml b/runtime/binding-filesystem/pom.xml index fd8bd67d2a..4534105606 100644 --- a/runtime/binding-filesystem/pom.xml +++ b/runtime/binding-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-grpc-kafka/pom.xml b/runtime/binding-grpc-kafka/pom.xml index a177227046..412de9c613 100644 --- a/runtime/binding-grpc-kafka/pom.xml +++ b/runtime/binding-grpc-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-grpc/pom.xml b/runtime/binding-grpc/pom.xml index 985c2be7ac..a98b6d6f85 100644 --- a/runtime/binding-grpc/pom.xml +++ b/runtime/binding-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java b/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java index 288ada9d69..aeb7efe820 100644 --- a/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java +++ b/runtime/binding-grpc/src/test/java/io/aklivity/zilla/runtime/binding/grpc/internal/config/GrpcOptionsConfigAdapterTest.java @@ -42,8 +42,8 @@ import io.aklivity.zilla.runtime.binding.grpc.config.GrpcProtobufConfig; import io.aklivity.zilla.runtime.binding.grpc.config.GrpcServiceConfig; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; -import io.aklivity.zilla.runtime.engine.internal.config.OptionsAdapter; public class GrpcOptionsConfigAdapterTest { @@ -52,7 +52,7 @@ public class GrpcOptionsConfigAdapterTest @Mock private ConfigAdapterContext context; - private OptionsAdapter adapter; + private OptionsConfigAdapter adapter; private Jsonb jsonb; @@ -67,7 +67,7 @@ public void initJson() throws IOException content = new String(resource.readAllBytes(), UTF_8); } Mockito.doReturn(content).when(context).readURL("protobuf/echo.proto"); - adapter = new OptionsAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); adapter.adaptType("grpc"); JsonbConfig config = new JsonbConfig() .withAdapters(adapter); diff --git a/runtime/binding-http-filesystem/pom.xml b/runtime/binding-http-filesystem/pom.xml index 021ec097c7..3205e3f980 100644 --- a/runtime/binding-http-filesystem/pom.xml +++ b/runtime/binding-http-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-http-kafka/pom.xml b/runtime/binding-http-kafka/pom.xml index e7973f1677..928e95b6ee 100644 --- a/runtime/binding-http-kafka/pom.xml +++ b/runtime/binding-http-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java index 18a69eddde..03e590e2a8 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfig.java @@ -14,6 +14,8 @@ */ package io.aklivity.zilla.runtime.binding.http.kafka.config; +import java.util.function.Function; + import io.aklivity.zilla.runtime.engine.config.ConditionConfig; public final class HttpKafkaConditionConfig extends ConditionConfig @@ -28,4 +30,15 @@ public HttpKafkaConditionConfig( this.method = method; this.path = path; } + + public static HttpKafkaConditionConfigBuilder builder() + { + return new HttpKafkaConditionConfigBuilder<>(HttpKafkaConditionConfig.class::cast); + } + + public static HttpKafkaConditionConfigBuilder builder( + Function mapper) + { + return new HttpKafkaConditionConfigBuilder<>(mapper); + } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java new file mode 100644 index 0000000000..3e8978424f --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaConditionConfigBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaConditionConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private String method; + private String path; + + public HttpKafkaConditionConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaConditionConfigBuilder method( + String method) + { + this.method = method; + return this; + } + + public HttpKafkaConditionConfigBuilder path( + String path) + { + this.path = path; + return this; + } + + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaConditionConfig(method, path)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfig.java similarity index 71% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfig.java index b49368d8a6..a12a8896ab 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfig.java @@ -12,10 +12,12 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; import java.util.Optional; +import java.util.function.Function; +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaCapability; import io.aklivity.zilla.runtime.engine.config.WithConfig; public final class HttpKafkaWithConfig extends WithConfig @@ -24,19 +26,18 @@ public final class HttpKafkaWithConfig extends WithConfig public final Optional fetch; public final Optional produce; - public HttpKafkaWithConfig( - HttpKafkaWithFetchConfig fetch) + public static HttpKafkaWithConfigBuilder builder() { - this(HttpKafkaCapability.FETCH, fetch, null); + return new HttpKafkaWithConfigBuilder<>(HttpKafkaWithConfig.class::cast); } - public HttpKafkaWithConfig( - HttpKafkaWithProduceConfig produce) + public static HttpKafkaWithConfigBuilder builder( + Function mapper) { - this(HttpKafkaCapability.PRODUCE, null, produce); + return new HttpKafkaWithConfigBuilder<>(mapper); } - private HttpKafkaWithConfig( + public HttpKafkaWithConfig( HttpKafkaCapability capability, HttpKafkaWithFetchConfig fetch, HttpKafkaWithProduceConfig produce) diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java new file mode 100644 index 0000000000..8f710170c3 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithConfigBuilder.java @@ -0,0 +1,65 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaCapability; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public final class HttpKafkaWithConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private HttpKafkaCapability capability; + private HttpKafkaWithFetchConfig fetch; + private HttpKafkaWithProduceConfig produce; + + HttpKafkaWithConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithConfigBuilder fetch( + HttpKafkaWithFetchConfig fetch) + { + capability = HttpKafkaCapability.FETCH; + this.fetch = fetch; + return this; + } + + public HttpKafkaWithConfigBuilder produce( + HttpKafkaWithProduceConfig produce) + { + capability = HttpKafkaCapability.PRODUCE; + this.produce = produce; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithConfig(capability, fetch, produce)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfig.java similarity index 66% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfig.java index 29538da6c7..7c201c8c0a 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfig.java @@ -12,10 +12,13 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; import java.util.List; import java.util.Optional; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaWithFetchFilterConfig; public final class HttpKafkaWithFetchConfig { @@ -32,4 +35,16 @@ public HttpKafkaWithFetchConfig( this.filters = Optional.ofNullable(filters); this.merge = Optional.ofNullable(merged); } + + public static HttpKafkaWithFetchConfigBuilder builder() + { + return new HttpKafkaWithFetchConfigBuilder<>(HttpKafkaWithFetchConfig.class::cast); + } + + public static HttpKafkaWithFetchConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithFetchConfigBuilder<>(mapper); + } + } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java new file mode 100644 index 0000000000..7edaec2ba0 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchConfigBuilder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.config.HttpKafkaWithFetchFilterConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithFetchConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String topic; + private List filters; + private HttpKafkaWithFetchMergeConfig merge; + + HttpKafkaWithFetchConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithFetchConfigBuilder topic( + String topic) + { + this.topic = topic; + return this; + } + + public HttpKafkaWithFetchConfigBuilder filters( + List filters) + { + this.filters = filters; + return this; + } + + public HttpKafkaWithFetchConfigBuilder merged( + HttpKafkaWithFetchMergeConfig merge) + { + this.merge = merge; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithFetchConfig(topic, filters, merge)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchMergeConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfig.java similarity index 65% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchMergeConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfig.java index 40c42f1123..26a426a5d0 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchMergeConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfig.java @@ -12,7 +12,9 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; public final class HttpKafkaWithFetchMergeConfig { @@ -29,4 +31,15 @@ public HttpKafkaWithFetchMergeConfig( this.initial = initial; this.path = path; } + + public static HttpKafkaWithFetchMergeConfigBuilder builder() + { + return new HttpKafkaWithFetchMergeConfigBuilder<>(HttpKafkaWithFetchMergeConfig.class::cast); + } + + public static HttpKafkaWithFetchMergeConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithFetchMergeConfigBuilder<>(mapper); + } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java new file mode 100644 index 0000000000..1b59132a2b --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithFetchMergeConfigBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithFetchMergeConfigBuilder extends + ConfigBuilder> +{ + private final Function mapper; + private String contentType; + private String initial; + private String path; + + + HttpKafkaWithFetchMergeConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithFetchMergeConfigBuilder contentType( + String contentType) + { + this.contentType = contentType; + return this; + } + + public HttpKafkaWithFetchMergeConfigBuilder initial( + String initial) + { + this.initial = initial; + return this; + } + + public HttpKafkaWithFetchMergeConfigBuilder path( + String path) + { + this.path = path; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithFetchMergeConfig(contentType, initial, path)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java new file mode 100644 index 0000000000..788a487b93 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +public final class HttpKafkaWithProduceAsyncHeaderConfig +{ + public final String name; + public final String value; + + public HttpKafkaWithProduceAsyncHeaderConfig( + String name, + String value) + { + this.name = name; + this.value = value; + } + + public static HttpKafkaWithProduceAsyncHeaderConfigBuilder builder() + { + return new HttpKafkaWithProduceAsyncHeaderConfigBuilder<>(HttpKafkaWithProduceAsyncHeaderConfig.class::cast); + } + + public static HttpKafkaWithProduceAsyncHeaderConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithProduceAsyncHeaderConfigBuilder<>(mapper); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java new file mode 100644 index 0000000000..2d595adf47 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceAsyncHeaderConfigBuilder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithProduceAsyncHeaderConfigBuilder extends + ConfigBuilder> +{ + private final Function mapper; + private String name; + private String value; + + + HttpKafkaWithProduceAsyncHeaderConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithProduceAsyncHeaderConfigBuilder name( + String name) + { + this.name = name; + return this; + } + + public HttpKafkaWithProduceAsyncHeaderConfigBuilder value( + String value) + { + this.value = value; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithProduceAsyncHeaderConfig(name, value)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfig.java similarity index 85% rename from runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfig.java rename to runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfig.java index 0bf34d99b3..746c60a7be 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfig.java @@ -12,12 +12,13 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.http.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.http.kafka.config; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Optional; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -82,4 +83,15 @@ private static Matcher asMatcher( .compile(wildcard.replaceAll("\\$\\{(?:params\\.)?([a-zA-Z_]+)\\}", "(?<$1>.+)")) .matcher(""); } + + public static HttpKafkaWithProduceConfigBuilder builder() + { + return new HttpKafkaWithProduceConfigBuilder<>(HttpKafkaWithProduceConfig.class::cast); + } + + public static HttpKafkaWithProduceConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithProduceConfigBuilder<>(mapper); + } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java new file mode 100644 index 0000000000..c494ac0980 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceConfigBuilder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithProduceConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String topic; + private KafkaAckMode acks; + private String key; + private List overrides; + private String replyTo; + private List async; + + + HttpKafkaWithProduceConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithProduceConfigBuilder topic( + String topic) + { + this.topic = topic; + return this; + } + + public HttpKafkaWithProduceConfigBuilder acks( + String acks) + { + this.acks = KafkaAckMode.valueOf(acks.toUpperCase()); + return this; + } + + public HttpKafkaWithProduceConfigBuilder key( + String key) + { + this.key = key; + return this; + } + + public HttpKafkaWithProduceConfigBuilder overrides( + List overrides) + { + this.overrides = overrides; + return this; + } + + public HttpKafkaWithProduceConfigBuilder replyTo( + String replyTo) + { + this.replyTo = replyTo; + return this; + } + + public HttpKafkaWithProduceConfigBuilder async( + List async) + { + this.async = async; + return this; + } + + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithProduceConfig(topic, acks, key, overrides, replyTo, async)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java new file mode 100644 index 0000000000..4fe3ffb770 --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfig.java @@ -0,0 +1,42 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +public final class HttpKafkaWithProduceOverrideConfig +{ + public final String name; + public final String value; + + public HttpKafkaWithProduceOverrideConfig( + String name, + String value) + { + this.name = name; + this.value = value; + } + + public static HttpKafkaWithProduceOverrideConfigBuilder builder() + { + return new HttpKafkaWithProduceOverrideConfigBuilder<>(HttpKafkaWithProduceOverrideConfig.class::cast); + } + + public static HttpKafkaWithProduceOverrideConfigBuilder builder( + Function mapper) + { + return new HttpKafkaWithProduceOverrideConfigBuilder<>(mapper); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java new file mode 100644 index 0000000000..aa0f33b61d --- /dev/null +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/config/HttpKafkaWithProduceOverrideConfigBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.http.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class HttpKafkaWithProduceOverrideConfigBuilder extends + ConfigBuilder> +{ + private final Function mapper; + private String name; + private String value; + + + HttpKafkaWithProduceOverrideConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + public HttpKafkaWithProduceOverrideConfigBuilder name( + String name) + { + this.name = name; + return this; + } + + public HttpKafkaWithProduceOverrideConfigBuilder value( + String value) + { + this.value = value; + return this; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + @Override + public T build() + { + return mapper.apply(new HttpKafkaWithProduceOverrideConfig(name, value)); + } +} diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java index c7c4dfff7d..59d959afae 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapter.java @@ -68,6 +68,9 @@ public ConditionConfig adaptFromJson( ? object.getString(PATH_NAME) : null; - return new HttpKafkaConditionConfig(method, path); + return HttpKafkaConditionConfig.builder() + .method(method) + .path(path) + .build(); } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java index 0fe8a77de5..e3874b1768 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaRouteConfig.java @@ -26,6 +26,7 @@ import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaConditionConfig; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; import io.aklivity.zilla.runtime.engine.config.RouteConfig; import io.aklivity.zilla.runtime.engine.util.function.LongObjectBiFunction; diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java index f87d5841f9..23bb755f16 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapter.java @@ -17,6 +17,7 @@ import jakarta.json.JsonObject; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; import io.aklivity.zilla.runtime.binding.http.kafka.internal.HttpKafkaBinding; import io.aklivity.zilla.runtime.engine.config.WithConfig; import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java index 7a7f324f0c..e8edee9089 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithFetchConfigAdapter.java @@ -24,6 +24,10 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; + public final class HttpKafkaWithFetchConfigAdapter implements JsonbAdapter { private static final String CAPABILITY_NAME = "capability"; @@ -166,10 +170,20 @@ public HttpKafkaWithConfig adaptFromJson( path = patch.getString(MERGE_PATCH_PATH_NAME); } - newMerged = new HttpKafkaWithFetchMergeConfig(contentType, initial, path); + newMerged = HttpKafkaWithFetchMergeConfig.builder() + .contentType(contentType) + .initial(initial) + .path(path) + .build(); } } - return new HttpKafkaWithConfig(new HttpKafkaWithFetchConfig(newTopic, newFilters, newMerged)); + return HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic(newTopic) + .filters(newFilters) + .merged(newMerged) + .build()) + .build(); } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java index 40285bef8c..51615ea3e8 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithProduceConfigAdapter.java @@ -22,6 +22,10 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceAsyncHeaderConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceOverrideConfig; import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode; public final class HttpKafkaWithProduceConfigAdapter implements JsonbAdapter @@ -114,7 +118,10 @@ public HttpKafkaWithConfig adaptFromJson( { String value = overrides.getString(name); - newOverrides.add(new HttpKafkaWithProduceOverrideConfig(name, value)); + newOverrides.add(HttpKafkaWithProduceOverrideConfig.builder() + .name(name) + .value(value) + .build()); } } @@ -132,11 +139,22 @@ public HttpKafkaWithConfig adaptFromJson( { String value = async.getString(name); - newAsync.add(new HttpKafkaWithProduceAsyncHeaderConfig(name, value)); + newAsync.add(HttpKafkaWithProduceAsyncHeaderConfig.builder() + .name(name) + .value(value) + .build()); } } - return new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig(newTopic, newAcks, newKey, newOverrides, newReplyTo, newAsync)); + return HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic(newTopic) + .acks(newAcks.name()) + .key(newKey) + .overrides(newOverrides) + .replyTo(newReplyTo) + .async(newAsync) + .build()) + .build(); } } diff --git a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java index 913828184d..498abb03d4 100644 --- a/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java +++ b/runtime/binding-http-kafka/src/main/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithResolver.java @@ -29,6 +29,12 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceAsyncHeaderConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceOverrideConfig; import io.aklivity.zilla.runtime.binding.http.kafka.internal.stream.HttpKafkaEtagHelper; import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.http.kafka.internal.types.HttpHeaderFW; diff --git a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java index 3b10e971e2..2102a46622 100644 --- a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java +++ b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaConditionConfigAdapterTest.java @@ -59,7 +59,10 @@ public void shouldReadCondition() @Test public void shouldWriteCondition() { - HttpKafkaConditionConfig condition = new HttpKafkaConditionConfig("GET", "/test"); + HttpKafkaConditionConfig condition = HttpKafkaConditionConfig.builder() + .method("GET") + .path("/test") + .build(); String text = jsonb.toJson(condition); diff --git a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java index 247ed4a33a..4ec1da57ac 100644 --- a/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java +++ b/runtime/binding-http-kafka/src/test/java/io/aklivity/zilla/runtime/binding/http/kafka/internal/config/HttpKafkaWithConfigAdapterTest.java @@ -18,7 +18,6 @@ import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresent; import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAnd; import static com.vtence.hamcrest.jpa.HasFieldWithValue.hasField; -import static io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode.IN_SYNC_REPLICAS; import static io.aklivity.zilla.runtime.binding.http.kafka.internal.types.KafkaAckMode.LEADER_ONLY; import static java.util.Collections.singletonList; import static org.hamcrest.MatcherAssert.assertThat; @@ -36,6 +35,13 @@ import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithFetchMergeConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceAsyncHeaderConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceConfig; +import io.aklivity.zilla.runtime.binding.http.kafka.config.HttpKafkaWithProduceOverrideConfig; + public class HttpKafkaWithConfigAdapterTest { private Jsonb jsonb; @@ -70,8 +76,13 @@ public void shouldReadWithFetchTopic() @Test public void shouldWriteWithFetchTopic() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithFetchConfig("test", null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic("test") + .filters(null) + .merged(null) + .build()) + .build(); String text = jsonb.toJson(with); @@ -115,15 +126,17 @@ public void shouldReadWithFetchTopicAndFilters() @Test public void shouldWriteWithFetchTopicAndFilters() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithFetchConfig( - "test", - singletonList(new HttpKafkaWithFetchFilterConfig( + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic("test") + .filters(singletonList(new HttpKafkaWithFetchFilterConfig( "fixed-key", singletonList(new HttpKafkaWithFetchFilterHeaderConfig( "tag", - "fixed-tag")))), - null)); + "fixed-tag"))))) + .merged(null) + .build()) + .build(); String text = jsonb.toJson(with); @@ -169,11 +182,17 @@ public void shouldReadWithFetchTopicAndMerge() @Test public void shouldWriteWithFetchTopicAndMerge() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithFetchConfig( - "test", - null, - new HttpKafkaWithFetchMergeConfig("application/json", "{\"data\":[]}", "/data/-"))); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .fetch(HttpKafkaWithFetchConfig.builder() + .topic("test") + .filters(null) + .merged(HttpKafkaWithFetchMergeConfig.builder() + .contentType("application/json") + .initial("{\"data\":[]}") + .path("/data/-") + .build()) + .build()) + .build(); String text = jsonb.toJson(with); @@ -210,8 +229,12 @@ public void shouldReadWithProduceTopic() @Test public void shouldWriteWithProduceTopic() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", IN_SYNC_REPLICAS, null, null, null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .build()) + .build(); String text = jsonb.toJson(with); @@ -246,8 +269,12 @@ public void shouldReadWithProduceTopicAndAcks() @Test public void shouldWriteWithProduceTopicAndAcks() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", LEADER_ONLY, null, null, null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("leader_only") + .build()) + .build(); String text = jsonb.toJson(with); @@ -281,8 +308,13 @@ public void shouldReadWithProduceTopicAndKey() @Test public void shouldWriteWithProduceTopicAndKey() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", IN_SYNC_REPLICAS, "${params.id}", null, null, null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .key("${params.id}") + .build()) + .build(); String text = jsonb.toJson(with); @@ -323,14 +355,16 @@ public void shouldReadWithProduceTopicAndOverrides() @Test public void shouldWriteWithProduceTopicAndOverrides() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig( - "test", - IN_SYNC_REPLICAS, - null, - singletonList(new HttpKafkaWithProduceOverrideConfig("id", "${params.id}")), - null, - null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .overrides(singletonList(HttpKafkaWithProduceOverrideConfig.builder() + .name("id") + .value("${params.id}") + .build())) + .build()) + .build(); String text = jsonb.toJson(with); @@ -364,8 +398,13 @@ public void shouldReadWithProduceTopicAndReplyTo() @Test public void shouldWriteWithProduceTopicAndReplyTo() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig("test", IN_SYNC_REPLICAS, null, null, "replies", null)); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .replyTo("replies") + .build()) + .build(); String text = jsonb.toJson(with); @@ -406,15 +445,17 @@ public void shouldReadWithProduceTopicAndAsync() @Test public void shouldWriteWithProduceTopicAndAsync() { - HttpKafkaWithConfig with = new HttpKafkaWithConfig( - new HttpKafkaWithProduceConfig( - "test", - IN_SYNC_REPLICAS, - null, - null, - null, - singletonList( - new HttpKafkaWithProduceAsyncHeaderConfig("location", "/items/${params.id};${correlationId}")))); + HttpKafkaWithConfig with = HttpKafkaWithConfig.builder() + .produce(HttpKafkaWithProduceConfig.builder() + .topic("test") + .acks("in_sync_replicas") + .async(singletonList( + HttpKafkaWithProduceAsyncHeaderConfig.builder() + .name("location") + .value("/items/${params.id};${correlationId}") + .build())) + .build()) + .build(); String text = jsonb.toJson(with); diff --git a/runtime/binding-http/pom.xml b/runtime/binding-http/pom.xml index b982106d4f..1180b24d2b 100644 --- a/runtime/binding-http/pom.xml +++ b/runtime/binding-http/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java index 7bb3baefd3..83b62ff766 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfig.java @@ -17,6 +17,8 @@ import static java.util.function.Function.identity; +import java.util.function.Function; + public final class HttpAuthorizationConfig { public final String name; @@ -27,6 +29,12 @@ public static HttpAuthorizationConfigBuilder builder() return new HttpAuthorizationConfigBuilder<>(identity()); } + public static HttpAuthorizationConfigBuilder builder( + Function mapper) + { + return new HttpAuthorizationConfigBuilder(mapper); + } + HttpAuthorizationConfig( String name, HttpCredentialsConfig credentials) diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java index 5f16beb53b..6a7bcff7d3 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpAuthorizationConfigBuilder.java @@ -26,19 +26,6 @@ public final class HttpAuthorizationConfigBuilder extends ConfigBuilder mapper) - { - this.mapper = mapper; - } - - @Override - @SuppressWarnings("unchecked") - protected Class> thisType() - { - return (Class>) getClass(); - } - public HttpAuthorizationConfigBuilder name( String name) { @@ -57,6 +44,20 @@ public T build() return mapper.apply(new HttpAuthorizationConfig(name, credentials)); } + + HttpAuthorizationConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + private HttpAuthorizationConfigBuilder credentials( HttpCredentialsConfig credentials) { diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java index 0271061d3c..bce304ea2f 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/config/HttpOptionsConfigBuilder.java @@ -101,7 +101,7 @@ public HttpRequestConfigBuilder> request() return new HttpRequestConfigBuilder<>(this::request); } - private HttpOptionsConfigBuilder authorization( + public HttpOptionsConfigBuilder authorization( HttpAuthorizationConfig authorization) { this.authorization = authorization; diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java new file mode 100644 index 0000000000..f4c0ee0fc1 --- /dev/null +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/HttpEventContext.java @@ -0,0 +1,102 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.http.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; +import java.util.Map; + +import org.agrona.concurrent.AtomicBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.http.internal.types.Array32FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.HttpHeaderFW; +import io.aklivity.zilla.runtime.binding.http.internal.types.String8FW; +import io.aklivity.zilla.runtime.binding.http.internal.types.event.HttpEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.guard.GuardHandler; + +public class HttpEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 2048; + private static final String8FW HEADER_SCHEME = new String8FW(":scheme"); + private static final String8FW HEADER_METHOD = new String8FW(":method"); + private static final String8FW HEADER_AUTHORITY = new String8FW(":authority"); + private static final String8FW HEADER_PATH = new String8FW(":path"); + + private final AtomicBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final HttpEventFW.Builder httpEventRW = new HttpEventFW.Builder(); + private final int httpTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public HttpEventContext( + EngineContext context) + { + this.httpTypeId = context.supplyTypeId(HttpBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void requestAccepted( + long traceId, + long bindingId, + GuardHandler guard, + long authorization, + Map headers) + { + String identity = guard == null ? null : guard.identity(authorization); + HttpEventFW event = httpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .requestAccepted(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + .scheme(headers.get(":scheme")) + .method(headers.get(":method")) + .authority(headers.get(":authority")) + .path(headers.get(":path")) + ) + .build(); + eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void requestAccepted( + long traceId, + long bindingId, + GuardHandler guard, + long authorization, + Array32FW headers) + { + String identity = guard == null ? null : guard.identity(authorization); + HttpEventFW event = httpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .requestAccepted(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + .scheme(headers.matchFirst(h -> HEADER_SCHEME.equals(h.name())).value().asString()) + .method(headers.matchFirst(h -> HEADER_METHOD.equals(h.name())).value().asString()) + .authority(headers.matchFirst(h -> HEADER_AUTHORITY.equals(h.name())).value().asString()) + .path(headers.matchFirst(h -> HEADER_PATH.equals(h.name())).value().asString()) + ) + .build(); + eventWriter.accept(httpTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java index 39b9bea739..7b6a5fc925 100644 --- a/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java +++ b/runtime/binding-http/src/main/java/io/aklivity/zilla/runtime/binding/http/internal/stream/HttpServerFactory.java @@ -84,6 +84,7 @@ import io.aklivity.zilla.runtime.binding.http.config.HttpVersion; import io.aklivity.zilla.runtime.binding.http.internal.HttpBinding; import io.aklivity.zilla.runtime.binding.http.internal.HttpConfiguration; +import io.aklivity.zilla.runtime.binding.http.internal.HttpEventContext; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ContinuationFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2DataFW; import io.aklivity.zilla.runtime.binding.http.internal.codec.Http2ErrorCode; @@ -544,6 +545,7 @@ public final class HttpServerFactory implements HttpStreamFactory private final Matcher connectionClose; private final int maximumHeadersSize; private final Long2ObjectHashMap bindings; + private final HttpEventContext event; public HttpServerFactory( HttpConfiguration config, @@ -576,6 +578,7 @@ public HttpServerFactory( this.supplyValidator = context::supplyValidator; this.encodeMax = bufferPool.slotCapacity(); this.bindings = new Long2ObjectHashMap<>(); + this.event = new HttpEventContext(context); this.headers200 = initHeaders(config, STATUS_200); this.headers204 = initHeaders(config, STATUS_204); @@ -1043,7 +1046,7 @@ else if (!isCorsRequestAllowed(server.binding, headers)) final String credentialsMatch = server.credentials.apply(headers::get); if (credentialsMatch != null) { - guard.reauthorize(server.initialId, credentialsMatch); + guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch); } server.doEncodeHeaders(traceId, authorization, budgetId, headers204); } @@ -1055,7 +1058,7 @@ else if (!isCorsRequestAllowed(server.binding, headers)) final String credentialsMatch = server.credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(server.initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, server.routedId, server.initialId, credentialsMatch); } } @@ -2268,6 +2271,7 @@ private boolean onDecodeHeaders( final HttpHeaderFW connection = beginEx.headers().matchFirst(h -> HEADER_CONNECTION.equals(h.name())); exchange.responseClosing = connection != null && connectionClose.reset(connection.value().asString()).matches(); + event.requestAccepted(traceId, routedId, guard, authorization, beginEx.headers()); this.exchange = exchange; } return headersValid; @@ -2293,7 +2297,7 @@ private int onDecodeBody( int limit, Flyweight extension) { - boolean contentValid = exchange.validateContent(buffer, 0, limit - offset); + boolean contentValid = exchange.validateContent(buffer, offset, limit - offset); int result; if (contentValid) { @@ -4923,7 +4927,7 @@ else if (headersDecoder.httpError()) else { final Map headers = headersDecoder.headers; - + event.requestAccepted(traceId, routedId, guard, authorization, headers); if (isCorsPreflightRequest(headers)) { if (!endRequest) @@ -4956,7 +4960,7 @@ else if (!isCorsRequestAllowed(binding, headers)) final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - guard.reauthorize(initialId, credentialsMatch); + guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } doEncodeHeaders(traceId, authorization, streamId, headers204, true); } @@ -4968,7 +4972,7 @@ else if (!isCorsRequestAllowed(binding, headers)) final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } @@ -5342,7 +5346,7 @@ private void doEncodePromise( final String credentialsMatch = credentials.apply(headers::get); if (credentialsMatch != null) { - exchangeAuth = guard.reauthorize(initialId, credentialsMatch); + exchangeAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } diff --git a/runtime/binding-kafka-grpc/pom.xml b/runtime/binding-kafka-grpc/pom.xml index b60fd1e182..354fec2fe7 100644 --- a/runtime/binding-kafka-grpc/pom.xml +++ b/runtime/binding-kafka-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-kafka/pom.xml b/runtime/binding-kafka/pom.xml index ce80a703cc..2e3c7ba66e 100644 --- a/runtime/binding-kafka/pom.xml +++ b/runtime/binding-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java index bba5a40275..e597ce09ac 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfig.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.stream.Stream; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -31,7 +32,18 @@ public final class KafkaOptionsConfig extends OptionsConfig public final List servers; public final KafkaSaslConfig sasl; - public KafkaOptionsConfig( + public static KafkaOptionsConfigBuilder builder() + { + return new KafkaOptionsConfigBuilder<>(KafkaOptionsConfig.class::cast); + } + + public static KafkaOptionsConfigBuilder builder( + Function mapper) + { + return new KafkaOptionsConfigBuilder<>(mapper); + } + + KafkaOptionsConfig( List bootstrap, List topics, List servers, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java new file mode 100644 index 0000000000..86ae0015a6 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaOptionsConfigBuilder.java @@ -0,0 +1,101 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public final class KafkaOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private List bootstrap; + private List topics; + private List servers; + private KafkaSaslConfig sasl; + + KafkaOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + this.topics = new ArrayList<>(); + this.servers = new ArrayList<>(); + this.bootstrap = new ArrayList<>(); + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaOptionsConfigBuilder bootstrap( + List bootstrap) + { + this.bootstrap = bootstrap; + return this; + } + + public KafkaOptionsConfigBuilder topics( + List topics) + { + this.topics = topics; + return this; + } + + public KafkaOptionsConfigBuilder topic( + KafkaTopicConfig topic) + { + this.topics.add(topic); + return this; + } + + public , C>> C topic( + Function>, C> topic) + { + return topic.apply(this::topic); + } + + public KafkaOptionsConfigBuilder servers( + List servers) + { + this.servers = servers; + return this; + } + + public KafkaOptionsConfigBuilder sasl( + KafkaSaslConfig sasl) + { + this.sasl = sasl; + return this; + } + + public , C>> C sasl( + Function>, C> sasl) + { + return sasl.apply(this::sasl); + } + + @Override + public T build() + { + return mapper.apply(new KafkaOptionsConfig(bootstrap, topics, servers, sasl)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java index 4860089a0c..7ee7197122 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfig.java @@ -15,13 +15,27 @@ */ package io.aklivity.zilla.runtime.binding.kafka.config; + +import java.util.function.Function; + public class KafkaSaslConfig { public final String mechanism; public final String username; public final String password; - public KafkaSaslConfig( + public static KafkaSaslConfigBuilder builder() + { + return new KafkaSaslConfigBuilder<>(KafkaSaslConfig.class::cast); + } + + public static KafkaSaslConfigBuilder builder( + Function mapper) + { + return new KafkaSaslConfigBuilder<>(mapper); + } + + KafkaSaslConfig( String mechanism, String username, String password) diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java new file mode 100644 index 0000000000..d0ab12dd06 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaSaslConfigBuilder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class KafkaSaslConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String mechanism; + private String username; + private String password; + + KafkaSaslConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaSaslConfigBuilder mechanism( + String mechanism) + { + this.mechanism = mechanism; + return this; + } + + public KafkaSaslConfigBuilder username( + String username) + { + this.username = username; + return this; + } + + public KafkaSaslConfigBuilder password( + String password) + { + this.password = password; + return this; + } + + @Override + public T build() + { + return mapper.apply(new KafkaSaslConfig(mechanism, username, password)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java index 8f605807fb..3b2dd559ca 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfig.java @@ -15,12 +15,25 @@ */ package io.aklivity.zilla.runtime.binding.kafka.config; +import java.util.function.Function; + public class KafkaServerConfig { public final String host; public final int port; - public KafkaServerConfig( + public static KafkaServerConfigBuilder builder() + { + return new KafkaServerConfigBuilder<>(KafkaServerConfig.class::cast); + } + + public static KafkaServerConfigBuilder builder( + Function mapper) + { + return new KafkaServerConfigBuilder<>(mapper); + } + + KafkaServerConfig( String host, int port) { diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java new file mode 100644 index 0000000000..dca827666e --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaServerConfigBuilder.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class KafkaServerConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String host; + private int port; + + KafkaServerConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaServerConfigBuilder host( + String host) + { + this.host = host; + return this; + } + + public KafkaServerConfigBuilder port( + int port) + { + this.port = port; + return this; + } + + @Override + public T build() + { + return mapper.apply(new KafkaServerConfig(host, port)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java index 8eafe82740..679a743b15 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfig.java @@ -16,6 +16,7 @@ package io.aklivity.zilla.runtime.binding.kafka.config; import java.util.Objects; +import java.util.function.Function; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaDeltaType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType; @@ -29,7 +30,18 @@ public class KafkaTopicConfig public final ModelConfig key; public final ModelConfig value; - public KafkaTopicConfig( + public static KafkaTopicConfigBuilder builder() + { + return new KafkaTopicConfigBuilder<>(KafkaTopicConfig.class::cast); + } + + public static KafkaTopicConfigBuilder builder( + Function mapper) + { + return new KafkaTopicConfigBuilder<>(mapper); + } + + KafkaTopicConfig( String name, KafkaOffsetType defaultOffset, KafkaDeltaType deltaType, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java new file mode 100644 index 0000000000..34a423b225 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/config/KafkaTopicConfigBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.config; + + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaDeltaType; +import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.ModelConfig; + +public final class KafkaTopicConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String name; + private KafkaOffsetType defaultOffset; + private KafkaDeltaType deltaType; + private ModelConfig key; + private ModelConfig value; + + KafkaTopicConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public KafkaTopicConfigBuilder name( + String name) + { + this.name = name; + return this; + } + + public KafkaTopicConfigBuilder defaultOffset( + KafkaOffsetType defaultOffset) + { + this.defaultOffset = defaultOffset; + return this; + } + + public KafkaTopicConfigBuilder deltaType( + KafkaDeltaType deltaType) + { + this.deltaType = deltaType; + return this; + } + + public KafkaTopicConfigBuilder key( + ModelConfig key) + { + this.key = key; + return this; + } + + public KafkaTopicConfigBuilder value( + ModelConfig value) + { + this.value = value; + return this; + } + + public , C>> C value( + Function>, C> value) + { + return value.apply(this::value); + } + + @Override + public T build() + { + return mapper.apply(new KafkaTopicConfig(name, defaultOffset, deltaType, key, value)); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java new file mode 100644 index 0000000000..86e8bc3168 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaEventContext.java @@ -0,0 +1,80 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.kafka.internal.types.event.KafkaEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class KafkaEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + private static final int ERROR_NONE = 0; + + private final KafkaEventFW.Builder kafkaEventRW = new KafkaEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int kafkaTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public KafkaEventContext( + EngineContext context) + { + this.kafkaTypeId = context.supplyTypeId(KafkaBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void authorizationFailed( + long traceId, + long bindingId) + { + KafkaEventFW event = kafkaEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .authorizationFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void apiVersionRejected( + long traceId, + long bindingId, + int apiKey, + int apiVersion) + { + KafkaEventFW event = kafkaEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .apiVersionRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .apiKey(apiKey) + .apiVersion(apiVersion) + ) + .build(); + eventWriter.accept(kafkaTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java index d590fead82..d88b2f81bb 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaBindingConfig.java @@ -15,13 +15,13 @@ */ package io.aklivity.zilla.runtime.binding.kafka.internal.config; +import static io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaTopicType.DEFAULT_TOPIC_TYPE; import static io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType.HISTORICAL; import static java.util.stream.Collectors.toList; +import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.function.ToLongFunction; -import java.util.stream.Collectors; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; @@ -32,7 +32,6 @@ import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.config.BindingConfig; import io.aklivity.zilla.runtime.engine.config.KindConfig; -import io.aklivity.zilla.runtime.engine.model.ConverterHandler; public final class KafkaBindingConfig { @@ -42,10 +41,7 @@ public final class KafkaBindingConfig public final KindConfig kind; public final List routes; public final ToLongFunction resolveId; - public final Map keyReaders; - public final Map keyWriters; - public final Map valueReaders; - public final Map valueWriters; + public final List topicTypes; public KafkaBindingConfig( BindingConfig binding, @@ -57,38 +53,8 @@ public KafkaBindingConfig( this.options = KafkaOptionsConfig.class.cast(binding.options); this.routes = binding.routes.stream().map(KafkaRouteConfig::new).collect(toList()); this.resolveId = binding.resolveId; - this.keyReaders = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.key != null - ? context.supplyReadConverter(t.key) - : ConverterHandler.NONE)) - : null; - this.keyWriters = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.key != null - ? context.supplyWriteConverter(t.key) - : ConverterHandler.NONE)) - : null; - this.valueReaders = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.value != null - ? context.supplyReadConverter(t.value) - : ConverterHandler.NONE)) - : null; - this.valueWriters = options != null && options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap( - t -> t.name, - t -> t.value != null - ? context.supplyWriteConverter(t.value) - : ConverterHandler.NONE)) - : null; + this.topicTypes = options != null && options.topics != null + ? options.topics.stream().map(t -> new KafkaTopicType(context, t)).collect(toList()) : Collections.emptyList(); } public KafkaRouteConfig resolve( @@ -147,27 +113,18 @@ public KafkaOffsetType supplyDefaultOffset( return config != null && config.defaultOffset != null ? config.defaultOffset : HISTORICAL; } - public ConverterHandler resolveKeyReader( + public KafkaTopicType resolveTopicType( String topic) { - return keyReaders != null ? keyReaders.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; - } - - public ConverterHandler resolveKeyWriter( - String topic) - { - return keyWriters != null ? keyWriters.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; - } - - public ConverterHandler resolveValueReader( - String topic) - { - return valueReaders != null ? valueReaders.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; - } - - public ConverterHandler resolveValueWriter( - String topic) - { - return valueWriters != null ? valueWriters.getOrDefault(topic, ConverterHandler.NONE) : ConverterHandler.NONE; + KafkaTopicType matchedType = DEFAULT_TOPIC_TYPE; + for (KafkaTopicType topicType : topicTypes) + { + if (topicType.matches(topic)) + { + matchedType = topicType; + break; + } + } + return matchedType; } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java index 42545a62d1..ec5e92b1e7 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapter.java @@ -29,6 +29,7 @@ import jakarta.json.bind.adapter.JsonbAdapter; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaOptionsConfigBuilder; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaSaslConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; @@ -114,7 +115,7 @@ public JsonObject adaptToJson( public OptionsConfig adaptFromJson( JsonObject object) { - + KafkaOptionsConfigBuilder optionsBuilder = KafkaOptionsConfig.builder(); JsonArray bootstrapArray = object.containsKey(BOOTSTRAP_NAME) ? object.getJsonArray(BOOTSTRAP_NAME) : null; @@ -131,29 +132,23 @@ public OptionsConfig adaptFromJson( ? object.getJsonObject(SASL_NAME) : null; - List bootstrap = null; - if (bootstrapArray != null) { - List bootstrap0 = new ArrayList<>(); - bootstrapArray.forEach(v -> bootstrap0.add(JsonString.class.cast(v).getString())); - bootstrap = bootstrap0; + List bootstrap = new ArrayList<>(); + bootstrapArray.forEach(v -> bootstrap.add(JsonString.class.cast(v).getString())); + optionsBuilder.bootstrap(bootstrap); } - List topics = null; - if (topicsArray != null) { - List topics0 = new ArrayList<>(); - topicsArray.forEach(v -> topics0.add(topic.adaptFromJson(v.asJsonObject()))); - topics = topics0; + List topics = new ArrayList<>(); + topicsArray.forEach(v -> topics.add(topic.adaptFromJson(v.asJsonObject()))); + optionsBuilder.topics(topics); } - List servers = null; - if (serversArray != null) { - List servers0 = new ArrayList<>(); + List servers = new ArrayList<>(); serversArray.forEach(v -> { final String server = JsonString.class.cast(v).getString(); @@ -163,23 +158,25 @@ public OptionsConfig adaptFromJson( final String host = matcher.group(1); final int port = Integer.parseInt(matcher.group(2)); - servers0.add(new KafkaServerConfig(host, port)); + servers.add(KafkaServerConfig.builder().host(host).port(port).build()); } }); - servers = servers0; + optionsBuilder.servers(servers); } - KafkaSaslConfig sasl = null; - if (saslObject != null) { final String mechanism = saslObject.getString(SASL_MECHANISM_NAME); final String username = saslObject.getString(SASL_PLAIN_USERNAME_NAME); final String password = saslObject.getString(SASL_PLAIN_PASSWORD_NAME); - sasl = new KafkaSaslConfig(mechanism, username, password); + optionsBuilder.sasl(KafkaSaslConfig.builder() + .mechanism(mechanism) + .username(username) + .password(password) + .build()); } - return new KafkaOptionsConfig(bootstrap, topics, servers, sasl); + return optionsBuilder.build(); } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java index 2fb99e5ca0..2e0ff908f1 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicConfigAdapter.java @@ -21,9 +21,9 @@ import jakarta.json.bind.adapter.JsonbAdapter; import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfigBuilder; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaDeltaType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaOffsetType; -import io.aklivity.zilla.runtime.engine.config.ModelConfig; import io.aklivity.zilla.runtime.engine.config.ModelConfigAdapter; public final class KafkaTopicConfigAdapter implements JsonbAdapter @@ -77,24 +77,24 @@ public JsonObject adaptToJson( public KafkaTopicConfig adaptFromJson( JsonObject object) { + KafkaTopicConfigBuilder topicBuilder = KafkaTopicConfig.builder(); String name = object.containsKey(NAME_NAME) - ? object.getString(NAME_NAME) - : null; + ? object.getString(NAME_NAME) + : null; + topicBuilder.name(name); - KafkaOffsetType defaultOffset = object.containsKey(DEFAULT_OFFSET_NAME) + topicBuilder.defaultOffset(object.containsKey(DEFAULT_OFFSET_NAME) ? KafkaOffsetType.valueOf(object.getString(DEFAULT_OFFSET_NAME).toUpperCase()) - : null; + : null); - KafkaDeltaType deltaType = object.containsKey(DELTA_TYPE_NAME) + topicBuilder.deltaType(object.containsKey(DELTA_TYPE_NAME) ? KafkaDeltaType.valueOf(object.getString(DELTA_TYPE_NAME).toUpperCase()) - : null; + : null); JsonObject key = object.containsKey(EVENT_KEY) ? object.getJsonObject(EVENT_KEY) : null; - ModelConfig keyConfig = null; - if (key != null) { JsonObjectBuilder keyObject = Json.createObjectBuilder(); @@ -102,15 +102,13 @@ public KafkaTopicConfig adaptFromJson( key.forEach(keyObject::add); keyObject.add(SUBJECT, name + "-key"); - keyConfig = converter.adaptFromJson(keyObject.build()); + topicBuilder.key(converter.adaptFromJson(keyObject.build())); } JsonObject value = object.containsKey(EVENT_VALUE) ? object.getJsonObject(EVENT_VALUE) : null; - ModelConfig valueConfig = null; - if (value != null) { JsonObjectBuilder valueObject = Json.createObjectBuilder(); @@ -118,9 +116,9 @@ public KafkaTopicConfig adaptFromJson( value.forEach(valueObject::add); valueObject.add(SUBJECT, name + "-value"); - valueConfig = converter.adaptFromJson(valueObject.build()); + topicBuilder.value(converter.adaptFromJson(valueObject.build())); } - return new KafkaTopicConfig(name, defaultOffset, deltaType, keyConfig, valueConfig); + return topicBuilder.build(); } } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java new file mode 100644 index 0000000000..cf217c1f58 --- /dev/null +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaTopicType.java @@ -0,0 +1,76 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.kafka.internal.config; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.aklivity.zilla.runtime.binding.kafka.config.KafkaTopicConfig; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.model.ConverterHandler; + +public class KafkaTopicType +{ + public static final KafkaTopicType DEFAULT_TOPIC_TYPE = new KafkaTopicType(); + + public final ConverterHandler keyReader; + public final ConverterHandler keyWriter; + public final ConverterHandler valueReader; + public final ConverterHandler valueWriter; + + private final Matcher topicMatch; + + private KafkaTopicType() + { + this.topicMatch = null; + this.keyReader = ConverterHandler.NONE; + this.keyWriter = ConverterHandler.NONE; + this.valueReader = ConverterHandler.NONE; + this.valueWriter = ConverterHandler.NONE; + } + + public KafkaTopicType( + EngineContext context, + KafkaTopicConfig topicConfig) + { + this.topicMatch = topicConfig.name != null ? asMatcher(topicConfig.name) : null; + this.keyReader = Optional.ofNullable(topicConfig.key) + .map(context::supplyReadConverter) + .orElse(ConverterHandler.NONE); + this.keyWriter = Optional.ofNullable(topicConfig.key) + .map(context::supplyWriteConverter) + .orElse(ConverterHandler.NONE); + this.valueReader = Optional.ofNullable(topicConfig.value) + .map(context::supplyReadConverter) + .orElse(ConverterHandler.NONE); + this.valueWriter = Optional.ofNullable(topicConfig.value) + .map(context::supplyWriteConverter) + .orElse(ConverterHandler.NONE); + } + + public boolean matches( + String topic) + { + return this.topicMatch == null || this.topicMatch.reset(topic).matches(); + } + + private static Matcher asMatcher( + String topic) + { + return Pattern.compile(topic.replaceAll("\\{[^}]+\\}", ".+")).matcher(""); + } +} diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java index 51edb9867b..9f71397961 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheClientProduceFactory.java @@ -48,6 +48,7 @@ import io.aklivity.zilla.runtime.binding.kafka.internal.cache.KafkaCacheTopic; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; +import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaTopicType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Flyweight; import io.aklivity.zilla.runtime.binding.kafka.internal.types.KafkaAckMode; @@ -260,11 +261,10 @@ public MessageConsumer newStream( final KafkaCache cache = supplyCache.apply(cacheName); final KafkaCacheTopic topic = cache.supplyTopic(topicName); final KafkaCachePartition partition = topic.supplyProducePartition(partitionId, localIndex); - final ConverterHandler convertKey = binding.resolveKeyWriter(topicName); - final ConverterHandler convertValue = binding.resolveValueWriter(topicName); + final KafkaTopicType topicType = binding.resolveTopicType(topicName); final KafkaCacheClientProduceFan newFan = new KafkaCacheClientProduceFan(routedId, resolvedId, authorization, budget, - partition, cacheRoute, topicName, convertKey, convertValue); + partition, cacheRoute, topicName, topicType); cacheRoute.clientProduceFansByTopicPartition.put(partitionKey, newFan); fan = newFan; @@ -537,8 +537,7 @@ private KafkaCacheClientProduceFan( KafkaCachePartition partition, KafkaCacheRoute cacheRoute, String topicName, - ConverterHandler convertKey, - ConverterHandler convertValue) + KafkaTopicType topicType) { this.originId = originId; this.routedId = routedId; @@ -548,8 +547,8 @@ private KafkaCacheClientProduceFan( this.budget = budget; this.cacheRoute = cacheRoute; this.topicName = topicName; - this.convertKey = convertKey; - this.convertValue = convertValue; + this.convertKey = topicType.keyWriter; + this.convertValue = topicType.valueWriter; this.members = new Long2ObjectHashMap<>(); this.defaultOffset = KafkaOffsetType.LIVE; this.cursor = cursorFactory.newCursor( diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java index 4d72d26725..b6b340e1ff 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerFetchFactory.java @@ -52,6 +52,7 @@ import io.aklivity.zilla.runtime.binding.kafka.internal.cache.KafkaCacheTopic; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; +import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaTopicType; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.ArrayFW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Flyweight; @@ -237,11 +238,10 @@ public MessageConsumer newStream( final KafkaCache cache = supplyCache.apply(cacheName); final KafkaCacheTopic cacheTopic = cache.supplyTopic(topicName); final KafkaCachePartition partition = cacheTopic.supplyFetchPartition(partitionId); - final ConverterHandler convertKey = binding.resolveKeyReader(topicName); - final ConverterHandler convertValue = binding.resolveValueReader(topicName); + final KafkaTopicType topicType = binding.resolveTopicType(topicName); final KafkaCacheServerFetchFanout newFanout = new KafkaCacheServerFetchFanout(routedId, resolvedId, authorization, - affinity, partition, routeDeltaType, defaultOffset, convertKey, convertValue); + affinity, partition, routeDeltaType, defaultOffset, topicType); cacheRoute.serverFetchFanoutsByTopicPartition.put(partitionKey, newFanout); fanout = newFanout; @@ -516,8 +516,7 @@ private KafkaCacheServerFetchFanout( KafkaCachePartition partition, KafkaDeltaType deltaType, KafkaOffsetType defaultOffset, - ConverterHandler convertKey, - ConverterHandler convertValue) + KafkaTopicType topicType) { this.originId = originId; this.routedId = routedId; @@ -528,8 +527,8 @@ private KafkaCacheServerFetchFanout( this.retentionMillisMax = defaultOffset == LIVE ? SECONDS.toMillis(30) : Long.MAX_VALUE; this.members = new ArrayList<>(); this.leaderId = leaderId; - this.convertKey = convertKey; - this.convertValue = convertValue; + this.convertKey = topicType.keyReader; + this.convertValue = topicType.valueReader; this.entryMark = new MutableInteger(0); this.valueMark = new MutableInteger(0); } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java index f1eaf5b83b..95c1a8dc53 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientDescribeFactory.java @@ -965,6 +965,7 @@ public void onDecodeResource( assert resource.equals(this.topic); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -975,6 +976,15 @@ public void onDecodeResource( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, + errorCode); + } + private void onNetwork( int msgTypeId, DirectBuffer buffer, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java index f71de6de0d..b44b82d71c 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientFetchFactory.java @@ -2949,6 +2949,7 @@ private void onDecodeOffsetsPartition( this.nextOffset = partitionOffset; break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); cleanupApplication(traceId, errorCode); doNetworkEnd(traceId, authorization); break; @@ -2988,6 +2989,10 @@ private void onDecodeFetchPartition( traceId, authorization, 0, EMPTY_OCTETS); } } + else + { + onDecodeResponseErrorCode(traceId, originId, errorCode); + } cleanupApplication(traceId, errorCode); doNetworkEnd(traceId, authorization); @@ -2995,6 +3000,14 @@ private void onDecodeFetchPartition( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, FETCH_API_KEY, FETCH_API_VERSION, errorCode); + } + private void onDecodeFetchTransactionAbort( long traceId, long authorization, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java index ba89f747c1..820ab60ab5 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientGroupFactory.java @@ -878,6 +878,8 @@ private int decodeFindCoordinatorResponse( findCoordinatorResponse.host(), findCoordinatorResponse.port()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, FIND_COORDINATOR_API_KEY, + FIND_COORDINATOR_API_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeClusterReject; break; @@ -1012,6 +1014,7 @@ private int decodeJoinGroupResponse( joinGroupResponse.memberId().asString()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, JOIN_GROUP_API_KEY, JOIN_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1071,6 +1074,7 @@ private int decodeSyncGroupResponse( client.onSyncGroupResponse(traceId, authorization, syncGroupResponse.assignment()); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, SYNC_GROUP_API_KEY, SYNC_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1133,6 +1137,7 @@ private int decodeHeartbeatResponse( client.onHeartbeatResponse(traceId, authorization); break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, HEARTBEAT_API_KEY, HEARTBEAT_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; break; @@ -1197,6 +1202,8 @@ private int decodeLeaveGroupResponse( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, + LEAVE_GROUP_VERSION, errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; } @@ -1211,6 +1218,8 @@ private int decodeLeaveGroupResponse( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, LEAVE_GROUP_API_KEY, LEAVE_GROUP_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeCoordinatorReject; } @@ -2440,7 +2449,10 @@ private void onFindCoordinator( delegate.nodeId = String.valueOf(nodeId); - KafkaServerConfig server = new KafkaServerConfig(host.asString(), port); + KafkaServerConfig server = KafkaServerConfig.builder() + .host(host.asString()) + .port(port) + .build(); delegate.client = new DescribeClient(originId, routedId, server, sasl, delegate); delegate.client.doNetworkBegin(traceId, authorization, 0); @@ -2565,11 +2577,20 @@ public void onDecodeResource( assert resource.equals(delegate.nodeId); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); onNetworkError(traceId, errorCode); break; } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, DESCRIBE_CONFIGS_API_KEY, DESCRIBE_CONFIGS_API_VERSION, errorCode); + } + private void onNetwork( int msgTypeId, DirectBuffer buffer, diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java index 94d2598daf..22994bc0c5 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientMetaFactory.java @@ -1786,7 +1786,7 @@ private void onDecodeBroker( String host, int port) { - newServers.put(brokerId, new KafkaServerConfig(host, port)); + newServers.put(brokerId, KafkaServerConfig.builder().host(host).port(port).build()); } private void onDecodeBrokers() @@ -1810,6 +1810,7 @@ private void onDecodeTopic( newPartitions.clear(); break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -1830,6 +1831,18 @@ private void onDecodePartition( { newPartitions.put(partitionId, leaderId); } + else + { + onDecodeResponseErrorCode(traceId, originId, partitionError); + } + } + + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, METADATA_API_KEY, METADATA_API_VERSION, errorCode); } @Override diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java index 7fec24693c..04771ff55a 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetCommitFactory.java @@ -208,7 +208,10 @@ public MessageConsumer newStream( final KafkaSaslConfig sasl = resolveSasl.apply(binding.sasl()); // TODO: use affinity (like meta, fetch, produce) instead of host and port - final KafkaServerConfig server = new KafkaServerConfig(host, port); + final KafkaServerConfig server = KafkaServerConfig.builder() + .host(host) + .port(port) + .build(); newStream = new KafkaOffsetCommitStream( application, @@ -589,6 +592,8 @@ private int decodeOffsetCommitPartition( } else { + client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_COMMIT_API_KEY, OFFSET_COMMIT_API_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeReject; } diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java index 35f415c6f7..38c0b38ccb 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientOffsetFetchFactory.java @@ -208,7 +208,10 @@ public MessageConsumer newStream( final KafkaSaslConfig sasl = resolveSasl.apply(binding.sasl()); // TODO: use affinity (like meta, fetch, produce) instead of host and port - final KafkaServerConfig server = new KafkaServerConfig(host, port); + final KafkaServerConfig server = KafkaServerConfig.builder() + .host(host) + .port(port) + .build(); newStream = new KafkaOffsetFetchStream( application, @@ -661,6 +664,8 @@ private int decodeOffsetFetchPartition( client.decoder = decodeOffsetFetchPartitions; break; default: + client.onDecodeResponseErrorCode(traceId, client.originId, OFFSET_FETCH_API_KEY, OFFSET_FETCH_API_VERSION, + errorCode); client.errorCode = errorCode; client.decoder = decodeReject; break; diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java index d21855ab20..9be9f026d2 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientProduceFactory.java @@ -42,6 +42,7 @@ import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaBinding; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration; +import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaBindingConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaRouteConfig; import io.aklivity.zilla.runtime.binding.kafka.internal.types.Array32FW; @@ -193,6 +194,7 @@ public final class KafkaClientProduceFactory extends KafkaClientSaslHandshaker i private final int decodeMaxBytes; private final int encodeMaxBytes; private final CRC32C crc32c; + private final KafkaEventContext event; public KafkaClientProduceFactory( KafkaConfiguration config, @@ -218,6 +220,7 @@ public KafkaClientProduceFactory( this.encodeMaxBytes = Math.min(config.clientProduceMaxBytes(), encodePool.slotCapacity() - PRODUCE_REQUEST_RECORDS_OFFSET_MAX); this.crc32c = new CRC32C(); + this.event = new KafkaEventContext(context); } @Override @@ -2268,6 +2271,7 @@ private void onDecodeProducePartition( assert partitionId == this.partitionId; break; default: + onDecodeResponseErrorCode(traceId, originId, errorCode); final KafkaResetExFW resetEx = kafkaResetExRW.wrap(extBuffer, 0, extBuffer.capacity()) .typeId(kafkaTypeId) .error(errorCode) @@ -2278,6 +2282,14 @@ private void onDecodeProducePartition( } } + private void onDecodeResponseErrorCode( + long traceId, + long originId, + int errorCode) + { + super.onDecodeResponseErrorCode(traceId, originId, PRODUCE_API_KEY, PRODUCE_API_VERSION, errorCode); + } + @Override protected void onDecodeSaslResponse( long traceId) diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java index b6a3b266c4..ca61063380 100644 --- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java +++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaClientSaslHandshaker.java @@ -43,6 +43,7 @@ import io.aklivity.zilla.runtime.binding.kafka.config.KafkaServerConfig; import io.aklivity.zilla.runtime.binding.kafka.identity.KafkaClientIdSupplier; import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaConfiguration; +import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaEventContext; import io.aklivity.zilla.runtime.binding.kafka.internal.config.KafkaScramMechanism; import io.aklivity.zilla.runtime.binding.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.binding.kafka.internal.types.codec.RequestHeaderFW; @@ -64,6 +65,7 @@ public abstract class KafkaClientSaslHandshaker private static final short SASL_AUTHENTICATE_API_VERSION = 1; private static final int ERROR_SASL_AUTHENTICATION_FAILED = 58; private static final int ERROR_NONE = 0; + private static final int ERROR_UNSUPPORTED_VERSION = 35; private static final String CLIENT_KEY = "Client Key"; private static final String SERVER_KEY = "Server Key"; @@ -92,6 +94,7 @@ public abstract class KafkaClientSaslHandshaker private final SaslHandshakeResponseFW saslHandshakeResponseRO = new SaslHandshakeResponseFW(); private final SaslHandshakeMechanismResponseFW saslHandshakeMechanismResponseRO = new SaslHandshakeMechanismResponseFW(); private final SaslAuthenticateResponseFW saslAuthenticateResponseRO = new SaslAuthenticateResponseFW(); + private final KafkaEventContext event; private KafkaSaslClientDecoder decodeSaslPlainAuthenticate = this::decodeSaslPlainAuthenticate; private KafkaSaslClientDecoder decodeSaslScramAuthenticateFirst = this::decodeSaslScramAuthenticateFirst; @@ -125,6 +128,7 @@ public KafkaClientSaslHandshaker( this.writeBuffer = new UnsafeBuffer(new byte[context.writeBuffer().capacity()]); this.nonceSupplier = config.nonceSupplier(); this.clientIdsByServer = new Object2ObjectHashMap<>(); + this.event = new KafkaEventContext(context); } public abstract class KafkaSaslClient @@ -423,6 +427,19 @@ private void doEncodeSaslScramFinalAuthenticateRequest( doDecodeSaslAuthenticateResponse(traceId); } + protected final void onDecodeResponseErrorCode( + long traceId, + long bindingId, + int apiKey, + int apiVersion, + int errorCode) + { + if (errorCode == ERROR_UNSUPPORTED_VERSION) + { + event.apiVersionRejected(traceId, bindingId, apiKey, apiVersion); + } + } + protected abstract void doNetworkData( long traceId, long budgetId, @@ -689,6 +706,10 @@ private int decodeSaslPlainAuthenticate( if (authenticateResponse != null) { final int errorCode = authenticateResponse.errorCode(); + if (errorCode != ERROR_NONE) + { + event.authorizationFailed(traceId, client.originId); + } progress = authenticateResponse.limit(); @@ -723,6 +744,10 @@ private int decodeSaslScramAuthenticateFirst( if (authenticateResponse != null) { final int errorCode = authenticateResponse.errorCode(); + if (errorCode != ERROR_NONE) + { + event.authorizationFailed(traceId, client.originId); + } progress = authenticateResponse.limit(); diff --git a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java index d94b68fa5f..d87d2c7137 100644 --- a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java +++ b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/config/KafkaOptionsConfigAdapterTest.java @@ -77,7 +77,8 @@ public void shouldReadOptions() assertThat(options, not(nullValue())); assertThat(options.bootstrap, equalTo(singletonList("test"))); - assertThat(options.topics, equalTo(singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, null)))); + assertThat(options.topics, equalTo(singletonList(KafkaTopicConfig.builder() + .name("test").defaultOffset(LIVE).deltaType(JSON_PATCH).build()))); assertThat(options.sasl.mechanism, equalTo("plain")); assertThat(options.sasl.username, equalTo("username")); assertThat(options.sasl.password, equalTo("password")); @@ -86,11 +87,24 @@ public void shouldReadOptions() @Test public void shouldWriteOptions() { - KafkaOptionsConfig options = new KafkaOptionsConfig( - singletonList("test"), - singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, TestModelConfig.builder().build())), - singletonList(new KafkaServerConfig("localhost", 9092)), - new KafkaSaslConfig("plain", "username", "password")); + KafkaOptionsConfig options = KafkaOptionsConfig.builder() + .bootstrap(singletonList("test")) + .topics( + singletonList(KafkaTopicConfig.builder() + .name("test") + .defaultOffset(LIVE) + .deltaType(JSON_PATCH) + .value(TestModelConfig.builder().build()) + .build())) + .servers(singletonList(KafkaServerConfig.builder() + .host("localhost") + .port(9092).build())) + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build(); String text = jsonb.toJson(options); @@ -132,7 +146,7 @@ public void shouldReadSaslScramOptions() assertThat(options, not(nullValue())); assertThat(options.bootstrap, equalTo(singletonList("test"))); assertThat(options.topics, equalTo(singletonList( - new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, null)))); + KafkaTopicConfig.builder().name("test").defaultOffset(LIVE).deltaType(JSON_PATCH).build()))); assertThat(options.sasl.mechanism, equalTo("scram-sha-256")); assertThat(options.sasl.username, equalTo("username")); assertThat(options.sasl.password, equalTo("password")); @@ -141,11 +155,23 @@ public void shouldReadSaslScramOptions() @Test public void shouldWriteSaslScramOptions() { - KafkaOptionsConfig options = new KafkaOptionsConfig( - singletonList("test"), - singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, null)), - singletonList(new KafkaServerConfig("localhost", 9092)), - new KafkaSaslConfig("scram-sha-256", "username", "password")); + KafkaOptionsConfig options = KafkaOptionsConfig.builder() + .bootstrap(singletonList("test")) + .topics(singletonList(KafkaTopicConfig.builder() + .name("test") + .defaultOffset(LIVE) + .deltaType(JSON_PATCH) + .build())) + .servers(singletonList(KafkaServerConfig.builder() + .host("localhost") + .port(9092) + .build())) + .sasl(KafkaSaslConfig.builder() + .mechanism("scram-sha-256") + .username("username") + .password("password") + .build()) + .build(); String text = jsonb.toJson(options); @@ -159,14 +185,24 @@ public void shouldWriteSaslScramOptions() @Test public void shouldWriteCatalogOptions() { - KafkaOptionsConfig options = new KafkaOptionsConfig( - singletonList("test"), - singletonList(new KafkaTopicConfig("test", LIVE, JSON_PATCH, null, - TestModelConfig.builder() - .length(0) - .build())), - singletonList(new KafkaServerConfig("localhost", 9092)), - new KafkaSaslConfig("plain", "username", "password")); + KafkaOptionsConfig options = KafkaOptionsConfig.builder() + .bootstrap(singletonList("test")) + .topics(singletonList(KafkaTopicConfig.builder() + .name("test") + .defaultOffset(LIVE) + .deltaType(JSON_PATCH) + .value(TestModelConfig.builder().length(0).build()) + .build())) + .servers(singletonList(KafkaServerConfig.builder() + .host("localhost") + .port(9092) + .build())) + .sasl(KafkaSaslConfig.builder() + .mechanism("plain") + .username("username") + .password("password") + .build()) + .build(); String text = jsonb.toJson(options); diff --git a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java index 3507a80750..dba2b931c1 100644 --- a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java +++ b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/CacheMergedIT.java @@ -257,6 +257,26 @@ public void shouldFetchMergedMessageValueValid() throws Exception k3po.finish(); } + @Test + @Configuration("cache.options.parameterized.topic.validate.yaml") + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.valid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.valid/server"}) + public void shouldFetchMergedFromParameterizedTopicValid() throws Exception + { + k3po.finish(); + } + + @Test + @Configuration("cache.options.parameterized.topic.validate.yaml") + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.invalid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.invalid/server"}) + public void shouldFetchMergedFromParameterizedTopicInvalid() throws Exception + { + k3po.finish(); + } + @Test @Configuration("cache.options.validate.yaml") @Specification({ diff --git a/runtime/binding-mqtt-kafka/pom.xml b/runtime/binding-mqtt-kafka/pom.xml index 72ab57255a..72450c8e77 100644 --- a/runtime/binding-mqtt-kafka/pom.xml +++ b/runtime/binding-mqtt-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java index 3b4747193c..a6483a85bc 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfig.java @@ -15,6 +15,7 @@ package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; import java.util.List; +import java.util.function.Function; import io.aklivity.zilla.runtime.engine.config.ConditionConfig; @@ -23,7 +24,18 @@ public class MqttKafkaConditionConfig extends ConditionConfig public final List topics; public final MqttKafkaConditionKind kind; - public MqttKafkaConditionConfig( + public static MqttKafkaConditionConfigBuilder builder() + { + return new MqttKafkaConditionConfigBuilder<>(MqttKafkaConditionConfig.class::cast); + } + + public static MqttKafkaConditionConfigBuilder builder( + Function mapper) + { + return new MqttKafkaConditionConfigBuilder<>(mapper); + } + + MqttKafkaConditionConfig( List topics, MqttKafkaConditionKind kind) { diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java new file mode 100644 index 0000000000..b01f32a24a --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaConditionConfigBuilder.java @@ -0,0 +1,63 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConditionConfig; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public final class MqttKafkaConditionConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private final List topics; + private MqttKafkaConditionKind kind; + + MqttKafkaConditionConfigBuilder( + Function mapper) + { + this.mapper = mapper; + this.topics = new ArrayList<>(); + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public MqttKafkaConditionConfigBuilder topic( + String topic) + { + this.topics.add(topic); + return this; + } + + public MqttKafkaConditionConfigBuilder kind( + MqttKafkaConditionKind kind) + { + this.kind = kind; + return this; + } + + @Override + public T build() + { + return mapper.apply(new MqttKafkaConditionConfig(topics, kind)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfig.java similarity index 68% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfig.java rename to runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfig.java index a7634f39bb..4c60c7c599 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfig.java @@ -12,9 +12,10 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; import java.util.List; +import java.util.function.Function; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -24,7 +25,18 @@ public class MqttKafkaOptionsConfig extends OptionsConfig public final String serverRef; public final List clients; - public MqttKafkaOptionsConfig( + public static MqttKafkaOptionsConfigBuilder builder() + { + return new MqttKafkaOptionsConfigBuilder<>(MqttKafkaOptionsConfig.class::cast); + } + + public static MqttKafkaOptionsConfigBuilder builder( + Function mapper) + { + return new MqttKafkaOptionsConfigBuilder<>(mapper); + } + + MqttKafkaOptionsConfig( MqttKafkaTopicsConfig topics, String serverRef, List clients) diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java new file mode 100644 index 0000000000..f1fc734608 --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaOptionsConfigBuilder.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.List; +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.OptionsConfig; + +public class MqttKafkaOptionsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private MqttKafkaTopicsConfig topics; + private String serverRef; + private List clients; + + MqttKafkaOptionsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + public MqttKafkaOptionsConfigBuilder topics( + MqttKafkaTopicsConfig topics) + { + this.topics = topics; + return this; + } + + public MqttKafkaTopicsConfigBuilder> topics() + { + return new MqttKafkaTopicsConfigBuilder<>(this::topics); + } + + public MqttKafkaOptionsConfigBuilder serverRef( + String serverRef) + { + this.serverRef = serverRef; + return this; + } + + public MqttKafkaOptionsConfigBuilder clients( + List clients) + { + this.clients = clients; + return this; + } + + + @Override + public T build() + { + return mapper.apply(new MqttKafkaOptionsConfig(topics, serverRef, clients)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaRouteConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java similarity index 89% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaRouteConfig.java rename to runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java index 3eea614b53..ed1d8ada79 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaRouteConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java @@ -12,7 +12,7 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; import static java.util.stream.Collectors.toList; @@ -20,8 +20,8 @@ import java.util.Optional; import java.util.function.LongPredicate; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaConditionMatcher; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaWithResolver; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.engine.config.RouteConfig; @@ -55,7 +55,7 @@ public MqttKafkaRouteConfig( this.authorized = route.authorized; } - boolean authorized( + public boolean authorized( long authorization) { return authorized.test(authorization); diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaTopicsConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfig.java similarity index 67% rename from runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaTopicsConfig.java rename to runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfig.java index 9a5c5066e1..c721daaf49 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaTopicsConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfig.java @@ -12,8 +12,9 @@ * WARRANTIES OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; +import java.util.function.Function; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; @@ -23,7 +24,18 @@ public class MqttKafkaTopicsConfig public final String16FW messages; public final String16FW retained; - public MqttKafkaTopicsConfig( + public static MqttKafkaTopicsConfigBuilder builder() + { + return new MqttKafkaTopicsConfigBuilder<>(MqttKafkaTopicsConfig.class::cast); + } + + public static MqttKafkaTopicsConfigBuilder builder( + Function mapper) + { + return new MqttKafkaTopicsConfigBuilder<>(mapper); + } + + MqttKafkaTopicsConfig( String16FW sessions, String16FW messages, String16FW retained) diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java new file mode 100644 index 0000000000..a35d35cbd1 --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaTopicsConfigBuilder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; + +public class MqttKafkaTopicsConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + + private String sessions; + private String messages; + private String retained; + + MqttKafkaTopicsConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + + public MqttKafkaTopicsConfigBuilder sessions( + String sessions) + { + this.sessions = sessions; + return this; + } + + public MqttKafkaTopicsConfigBuilder messages( + String messages) + { + this.messages = messages; + return this; + } + + public MqttKafkaTopicsConfigBuilder retained( + String retained) + { + this.retained = retained; + return this; + } + + + @Override + public T build() + { + return mapper.apply( + new MqttKafkaTopicsConfig(new String16FW(sessions), new String16FW(messages), new String16FW(retained))); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java new file mode 100644 index 0000000000..7a1cebb8d6 --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public class MqttKafkaWithConfig extends WithConfig +{ + public final String messages; + + public static MqttKafkaWithConfigBuilder builder() + { + return new MqttKafkaWithConfigBuilder<>(MqttKafkaWithConfig.class::cast); + } + + public static MqttKafkaWithConfigBuilder builder( + Function mapper) + { + return new MqttKafkaWithConfigBuilder<>(mapper); + } + + MqttKafkaWithConfig( + String messages) + { + this.messages = messages; + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java new file mode 100644 index 0000000000..3bb635e78e --- /dev/null +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaWithConfigBuilder.java @@ -0,0 +1,51 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.binding.mqtt.kafka.config; + +import java.util.function.Function; + +import io.aklivity.zilla.runtime.engine.config.ConfigBuilder; +import io.aklivity.zilla.runtime.engine.config.WithConfig; + +public final class MqttKafkaWithConfigBuilder extends ConfigBuilder> +{ + private final Function mapper; + private String messages; + + MqttKafkaWithConfigBuilder( + Function mapper) + { + this.mapper = mapper; + } + + @Override + @SuppressWarnings("unchecked") + protected Class> thisType() + { + return (Class>) getClass(); + } + + public MqttKafkaWithConfigBuilder messages( + String messages) + { + this.messages = messages; + return this; + } + @Override + public T build() + { + return mapper.apply(new MqttKafkaWithConfig(messages)); + } +} diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java index afcd7c7d6e..a152f8f2d0 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaBindingConfig.java @@ -25,6 +25,8 @@ import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaSessionFactory; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttTopicFilterFW; diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java index 2902f92a1d..92c447c090 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapter.java @@ -14,9 +14,6 @@ */ package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; -import java.util.ArrayList; -import java.util.List; - import jakarta.json.Json; import jakarta.json.JsonArray; import jakarta.json.JsonArrayBuilder; @@ -25,6 +22,7 @@ import jakarta.json.bind.adapter.JsonbAdapter; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfigBuilder; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaBinding; import io.aklivity.zilla.runtime.engine.config.ConditionConfig; @@ -74,30 +72,21 @@ else if (mqttKafkaCondition.kind == MqttKafkaConditionKind.PUBLISH) public ConditionConfig adaptFromJson( JsonObject object) { - List topics = new ArrayList<>(); - MqttKafkaConditionKind kind = null; + MqttKafkaConditionConfigBuilder builder = MqttKafkaConditionConfig.builder(); if (object.containsKey(SUBSCRIBE_NAME)) { - kind = MqttKafkaConditionKind.SUBSCRIBE; + builder.kind(MqttKafkaConditionKind.SUBSCRIBE); JsonArray subscribesJson = object.getJsonArray(SUBSCRIBE_NAME); - subscribesJson.forEach(s -> - { - String topic = s.asJsonObject().getString(TOPIC_NAME); - topics.add(topic); - }); + subscribesJson.forEach(s -> builder.topic(s.asJsonObject().getString(TOPIC_NAME))); } else if (object.containsKey(PUBLISH_NAME)) { - kind = MqttKafkaConditionKind.PUBLISH; + builder.kind(MqttKafkaConditionKind.PUBLISH); JsonArray publishesJson = object.getJsonArray(PUBLISH_NAME); - publishesJson.forEach(p -> - { - String topic = p.asJsonObject().getString(TOPIC_NAME); - topics.add(topic); - }); + publishesJson.forEach(p -> builder.topic(p.asJsonObject().getString(TOPIC_NAME))); } - return new MqttKafkaConditionConfig(topics, kind); + return builder.build(); } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java index a9a0ded706..bd6f9662d9 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapter.java @@ -25,6 +25,9 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfigBuilder; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaTopicsConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaBinding; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; @@ -104,8 +107,9 @@ public JsonObject adaptToJson( public OptionsConfig adaptFromJson( JsonObject object) { + MqttKafkaOptionsConfigBuilder options = MqttKafkaOptionsConfig.builder(); JsonObject topics = object.getJsonObject(TOPICS_NAME); - String server = object.getString(SERVER_NAME, null); + options.serverRef(object.getString(SERVER_NAME, null)); JsonArray clientsJson = object.getJsonArray(CLIENTS_NAME); List clients = new ArrayList<>(); @@ -116,13 +120,14 @@ public OptionsConfig adaptFromJson( clients.add(clientsJson.getString(i)); } } + options.clients(clients); - String16FW newSessions = new String16FW(topics.getString(SESSIONS_NAME)); - String16FW newMessages = new String16FW(topics.getString(MESSAGES_NAME)); - String16FW newRetained = new String16FW(topics.getString(RETAINED_NAME)); + options.topics(MqttKafkaTopicsConfig.builder() + .sessions(topics.getString(SESSIONS_NAME)) + .messages(topics.getString(MESSAGES_NAME)) + .retained(topics.getString(RETAINED_NAME)) + .build()); - MqttKafkaTopicsConfig newTopics = new MqttKafkaTopicsConfig(newSessions, newMessages, newRetained); - - return new MqttKafkaOptionsConfig(newTopics, server, clients); + return options.build(); } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java index 0f345bf758..eaf7b913e9 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapter.java @@ -19,6 +19,7 @@ import jakarta.json.JsonObjectBuilder; import jakarta.json.bind.adapter.JsonbAdapter; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaBinding; import io.aklivity.zilla.runtime.engine.config.WithConfig; import io.aklivity.zilla.runtime.engine.config.WithConfigAdapterSpi; @@ -57,6 +58,8 @@ public WithConfig adaptFromJson( ? object.getString(MESSAGES_NAME) : null; - return new MqttKafkaWithConfig(topic); + return MqttKafkaWithConfig.builder() + .messages(topic) + .build(); } } diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java index 4d8850821e..d0ca895a2c 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithResolver.java @@ -15,6 +15,8 @@ package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; public class MqttKafkaWithResolver diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java index 9b9a809083..2554a12fe6 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishFactory.java @@ -36,10 +36,10 @@ import org.agrona.collections.Long2ObjectHashMap; import org.agrona.concurrent.UnsafeBuffer; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaBindingConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaHeaderHelper; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaGroup; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaOffsetMetadata; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaTopicPartition; @@ -793,7 +793,7 @@ private void doMqttWindow( final int newInitialMax = retainedFlag ? Math.max(messages.initialMax, retained.initialMax) : messages.initialMax; if (MqttKafkaState.initialOpened(messages.state) && - (!retainedFlag || MqttKafkaState.initialOpened(retained.state)) && + (!retainAvailable || MqttKafkaState.initialOpened(retained.state)) && (initialAck != newInitialAck || initialMax != newInitialMax)) { initialAck = newInitialAck; diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java index 4d371e38ed..4e0e7a7e40 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSessionFactory.java @@ -49,11 +49,11 @@ import org.agrona.collections.Object2ObjectHashMap; import org.agrona.concurrent.UnsafeBuffer; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.InstanceId; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaBindingConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaHeaderHelper; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaGroup; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaOffsetMetadata; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaPublishMetadata.KafkaOffsetMetadataHelper; @@ -1431,6 +1431,7 @@ private void doCreateSessionStream( .flags(sessionFlags) .expiry((int) TimeUnit.MILLISECONDS.toSeconds(sessionExpiryMillis)) .subscribeQosMax(MQTT_KAFKA_MAX_QOS) + .publishQosMax(MQTT_KAFKA_MAX_QOS) .capabilities(MQTT_KAFKA_CAPABILITIES) .clientId(clientId); @@ -1912,9 +1913,12 @@ private void doKafkaEnd( { if (!MqttKafkaState.initialClosed(state)) { - state = MqttKafkaState.closeInitial(state); + if (MqttKafkaState.initialOpening(state)) + { + state = MqttKafkaState.closeInitial(state); - doEnd(kafka, originId, routedId, initialId, 0, 0, 0, traceId, authorization); + doEnd(kafka, originId, routedId, initialId, 0, 0, 0, traceId, authorization); + } signaler.cancel(reconnectAt); reconnectAt = NO_CANCEL_ID; @@ -4086,8 +4090,11 @@ private void onKafkaData( final KafkaMetaDataExFW kafkaMetaDataEx = kafkaDataEx.meta(); final Array32FW partitions = kafkaMetaDataEx.partitions(); - delegate.onPartitionsFetched(traceId, authorization, topic, partitions, this); - doKafkaEnd(traceId, authorization); + if (!MqttKafkaState.initialClosed(state)) + { + delegate.onPartitionsFetched(traceId, authorization, topic, partitions, this); + doKafkaEnd(traceId, authorization); + } } private void onKafkaEnd( diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java index fde1562c52..1f53782aaf 100644 --- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java +++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeFactory.java @@ -14,7 +14,6 @@ */ package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream; - import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.stream.MqttKafkaSessionFactory.MQTT_CLIENTS_GROUP_ID; import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttPublishFlags.RETAIN; import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttSubscribeFlags.NO_LOCAL; @@ -26,7 +25,6 @@ import static java.time.Instant.now; import static java.util.concurrent.TimeUnit.SECONDS; - import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -47,10 +45,10 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionKind; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaBindingConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaHeaderHelper; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config.MqttKafkaRouteConfig; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.Array32FW; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.Flyweight; import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.KafkaCapabilities; diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java index e739a320f4..761ce1a449 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionConfigAdapterTest.java @@ -19,8 +19,6 @@ import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; -import java.util.List; - import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; @@ -88,7 +86,10 @@ public void shouldReadCondition() @Test public void shouldWriteSubscribeCondition() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig(List.of("test"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("test") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); String text = jsonb.toJson(condition); @@ -107,7 +108,10 @@ public void shouldWriteSubscribeCondition() @Test public void shouldWritePublishCondition() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig(List.of("test"), MqttKafkaConditionKind.PUBLISH); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("test") + .kind(MqttKafkaConditionKind.PUBLISH) + .build(); String text = jsonb.toJson(condition); diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java index 9afe0f9459..6331b0384f 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcherTest.java @@ -17,8 +17,6 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; -import java.util.List; - import org.junit.Test; import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaConditionConfig; @@ -29,8 +27,10 @@ public class MqttKafkaConditionMatcherTest @Test public void shouldMatchSimpleConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig(List.of("/some/hierarchical/topic/name"), - MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic/name") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertTrue(matcher.matches("/some/hierarchical/topic/name")); @@ -47,9 +47,10 @@ public void shouldMatchSimpleConditions() @Test public void shouldNotMatchSimpleConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("/some/hierarchical/topic/name"), - MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic/name") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertFalse(matcher.matches("/some/+")); @@ -61,8 +62,10 @@ public void shouldNotMatchSimpleConditions() @Test public void shouldMatchSimpleConditions2() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("/some/hierarchical/topic"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertTrue(matcher.matches("/some/hierarchical/topic")); @@ -79,8 +82,10 @@ public void shouldMatchSimpleConditions2() @Test public void shouldNotMatchSimpleConditions2() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("/some/hierarchical/topic"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("/some/hierarchical/topic") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertFalse(matcher.matches("/some/+")); @@ -92,8 +97,10 @@ public void shouldNotMatchSimpleConditions2() @Test public void shouldMatchWildcardConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("device/#"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("device/#") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertTrue(matcher.matches("device/one")); @@ -107,8 +114,10 @@ public void shouldMatchWildcardConditions() @Test public void shouldNotMatchWildcardConditions() { - MqttKafkaConditionConfig condition = new MqttKafkaConditionConfig( - List.of("device/#"), MqttKafkaConditionKind.SUBSCRIBE); + MqttKafkaConditionConfig condition = MqttKafkaConditionConfig.builder() + .topic("device/#") + .kind(MqttKafkaConditionKind.SUBSCRIBE) + .build(); MqttKafkaConditionMatcher matcher = new MqttKafkaConditionMatcher(condition); assertFalse(matcher.matches("/device/one")); diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java index cc74c235da..c69492f9ca 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaOptionsConfigAdapterTest.java @@ -28,7 +28,8 @@ import org.junit.Before; import org.junit.Test; -import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaTopicsConfig; public class MqttKafkaOptionsConfigAdapterTest { @@ -78,13 +79,15 @@ public void shouldReadOptions() @Test public void shouldWriteOptions() { - MqttKafkaOptionsConfig options = new MqttKafkaOptionsConfig( - new MqttKafkaTopicsConfig( - new String16FW("sessions"), - new String16FW("messages"), - new String16FW("retained")), - "mqtt-1.example.com:1883", - Arrays.asList("/clients/{identity}/#", "/department/clients/{identity}/#")); + MqttKafkaOptionsConfig options = MqttKafkaOptionsConfig.builder() + .topics(MqttKafkaTopicsConfig.builder() + .sessions("sessions") + .messages("messages") + .retained("retained") + .build()) + .serverRef("mqtt-1.example.com:1883") + .clients(Arrays.asList("/clients/{identity}/#", "/department/clients/{identity}/#")) + .build(); String text = jsonb.toJson(options); @@ -131,11 +134,13 @@ public void shouldReadOptionsWithoutClients() @Test public void shouldWriteOptionsWithoutClients() { - MqttKafkaOptionsConfig options = new MqttKafkaOptionsConfig( - new MqttKafkaTopicsConfig( - new String16FW("sessions"), - new String16FW("messages"), - new String16FW("retained")), null, null); + MqttKafkaOptionsConfig options = MqttKafkaOptionsConfig.builder() + .topics(MqttKafkaTopicsConfig.builder() + .sessions("sessions") + .messages("messages") + .retained("retained") + .build()) + .build(); String text = jsonb.toJson(options); diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java index 0662393ef2..a4f5cd3b94 100644 --- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java +++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaWithConfigAdapterTest.java @@ -26,6 +26,8 @@ import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig; + public class MqttKafkaWithConfigAdapterTest { private Jsonb jsonb; @@ -53,7 +55,7 @@ public void shouldReadWith() @Test public void shouldWriteWith() { - MqttKafkaWithConfig with = new MqttKafkaWithConfig("test"); + MqttKafkaWithConfig with = MqttKafkaWithConfig.builder().messages("test").build(); String text = jsonb.toJson(with); diff --git a/runtime/binding-mqtt/pom.xml b/runtime/binding-mqtt/pom.xml index 74bde8da4d..a538705948 100644 --- a/runtime/binding-mqtt/pom.xml +++ b/runtime/binding-mqtt/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java index 875be68ee9..3242ec645a 100644 --- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java +++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/config/MqttBindingConfig.java @@ -18,13 +18,13 @@ import static java.util.stream.Collectors.toList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; import java.util.function.ToLongFunction; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import io.aklivity.zilla.runtime.binding.mqtt.config.MqttCredentialsConfig; import io.aklivity.zilla.runtime.binding.mqtt.config.MqttOptionsConfig; @@ -47,7 +47,7 @@ public final class MqttBindingConfig public final MqttOptionsConfig options; public final List routes; public final Function credentials; - public final Map topics; + public final Map topics; public final List versions; public final ToLongFunction resolveId; public final GuardHandler guard; @@ -64,10 +64,20 @@ public MqttBindingConfig( this.resolveId = binding.resolveId; this.credentials = options != null && options.authorization != null ? asAccessor(options.authorization.credentials) : DEFAULT_CREDENTIALS; - this.topics = options != null && - options.topics != null - ? options.topics.stream() - .collect(Collectors.toMap(t -> t.name, t -> t.content)) : null; + this.topics = new HashMap<>(); + if (options != null && options.topics != null) + { + options.topics.forEach(t -> + { + String pattern = t.name.replace(".", "\\.") + .replace("$", "\\$") + .replace("+", "[^/]*") + .replace("#", ".*"); + topics.put(Pattern.compile(pattern).matcher(""), t.content); + }); + } + + this.guard = resolveGuard(context); this.versions = options != null && options.versions != null ? options.versions : DEFAULT_VERSIONS; @@ -115,7 +125,21 @@ public MqttRouteConfig resolvePublish( public ModelConfig supplyModelConfig( String topic) { - return topics != null ? topics.getOrDefault(topic, null) : null; + ModelConfig config = null; + if (topics != null) + { + for (Map.Entry t : topics.entrySet()) + { + final Matcher matcher = t.getKey(); + matcher.reset(topic); + if (matcher.find()) + { + config = t.getValue(); + break; + } + } + } + return config; } public Function credentials() diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java index 1c681d82ca..7d8e6a7f5b 100644 --- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java +++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttServerFactory.java @@ -2910,7 +2910,7 @@ else if (this.authField.equals(MqttConnectProperty.PASSWORD)) if (credentialsMatch != null) { - sessionAuth = guard.reauthorize(initialId, credentialsMatch); + sessionAuth = guard.reauthorize(traceId, routedId, initialId, credentialsMatch); } } diff --git a/runtime/binding-proxy/pom.xml b/runtime/binding-proxy/pom.xml index 204c628e7a..7cc9e5fdfb 100644 --- a/runtime/binding-proxy/pom.xml +++ b/runtime/binding-proxy/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-sse-kafka/pom.xml b/runtime/binding-sse-kafka/pom.xml index 03fd214708..3599cc0ff0 100644 --- a/runtime/binding-sse-kafka/pom.xml +++ b/runtime/binding-sse-kafka/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-sse/pom.xml b/runtime/binding-sse/pom.xml index b18ff9fb85..c9a0939f04 100644 --- a/runtime/binding-sse/pom.xml +++ b/runtime/binding-sse/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/binding-tcp/pom.xml b/runtime/binding-tcp/pom.xml index 14c635d37b..e15efd6f4a 100644 --- a/runtime/binding-tcp/pom.xml +++ b/runtime/binding-tcp/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.89 + 0.88 0 @@ -123,7 +123,7 @@ flyweight-maven-plugin ${project.version} - core proxy + core tcp proxy io.aklivity.zilla.runtime.binding.tcp.internal.types diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java new file mode 100644 index 0000000000..7159725444 --- /dev/null +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/TcpEventContext.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.tcp.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.tcp.internal.types.event.TcpEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class TcpEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final TcpEventFW.Builder tcpEventRW = new TcpEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int tcpTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public TcpEventContext( + EngineContext context) + { + this.tcpTypeId = context.supplyTypeId(TcpBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void dnsResolutionFailed( + long traceId, + long bindingId, + String address) + { + TcpEventFW event = tcpEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .dnsFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .address(address) + ) + .build(); + eventWriter.accept(tcpTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java index 08afe896f8..cc3d8645f9 100644 --- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientFactory.java @@ -138,6 +138,7 @@ public MessageConsumer newStream( MessageConsumer application) { final BeginFW begin = beginRO.wrap(buffer, index, index + length); + final long traceId = begin.traceId(); final long originId = begin.originId(); final long routedId = begin.routedId(); final long authorization = begin.authorization(); @@ -151,7 +152,7 @@ public MessageConsumer newStream( TcpBindingConfig binding = router.lookup(routedId); if (binding != null) { - route = router.resolve(binding, authorization, beginEx); + route = router.resolve(binding, traceId, authorization, beginEx); } MessageConsumer newStream = null; @@ -257,13 +258,14 @@ private void doNetConnect( state = TcpState.openingInitial(state); net.setOption(SO_KEEPALIVE, options != null && options.keepalive); + networkKey = supplyPollerKey.apply(net); + if (net.connect(remoteAddress)) { onNetConnected(); } else { - networkKey = supplyPollerKey.apply(net); networkKey.handler(OP_CONNECT, this::onNetConnect); networkKey.register(OP_CONNECT); } @@ -283,7 +285,7 @@ private int onNetConnect( net.finishConnect(); onNetConnected(); } - catch (UnresolvedAddressException | IOException ex) + catch (IOException ex) { onNetRejected(); } diff --git a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java index ecfb709157..fc343d4e2b 100644 --- a/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java +++ b/runtime/binding-tcp/src/main/java/io/aklivity/zilla/runtime/binding/tcp/internal/stream/TcpClientRouter.java @@ -30,6 +30,7 @@ import org.agrona.collections.Long2ObjectHashMap; import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig; +import io.aklivity.zilla.runtime.binding.tcp.internal.TcpEventContext; import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpBindingConfig; import io.aklivity.zilla.runtime.binding.tcp.internal.config.TcpRouteConfig; import io.aklivity.zilla.runtime.binding.tcp.internal.types.Array32FW; @@ -49,12 +50,14 @@ public final class TcpClientRouter private final Function resolveHost; private final Long2ObjectHashMap bindings; + private final TcpEventContext event; public TcpClientRouter( EngineContext context) { this.resolveHost = context::resolveHost; this.bindings = new Long2ObjectHashMap<>(); + this.event = new TcpEventContext(context); } public void attach( @@ -71,6 +74,7 @@ public TcpBindingConfig lookup( public InetSocketAddress resolve( TcpBindingConfig binding, + long traceId, long authorization, ProxyBeginExFW beginEx) { @@ -79,86 +83,106 @@ public InetSocketAddress resolve( InetSocketAddress resolved = null; - if (beginEx == null) - { - resolved = options != null ? new InetSocketAddress(options.host, port) : null; - } - else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES) - { - ProxyAddressFW address = beginEx.address(); - resolved = resolveInetSocketAddress(address); - } - else + try { - final ProxyAddressFW address = beginEx.address(); - - for (TcpRouteConfig route : binding.routes) + if (beginEx == null) { - if (!route.authorized(authorization)) - { - continue; - } + InetAddress[] addresses = options != null ? resolveHost(options.host) : null; + resolved = addresses != null ? new InetSocketAddress(addresses[0], port) : null; + } + else if (binding.routes == TcpBindingConfig.DEFAULT_CLIENT_ROUTES) + { + ProxyAddressFW address = beginEx.address(); + resolved = resolveInetSocketAddress(address); + } + else + { + final ProxyAddressFW address = beginEx.address(); - Array32FW infos = beginEx.infos(); - ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY); - if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null)) + for (TcpRouteConfig route : binding.routes) { - final List authorities = Arrays - .stream(resolveHost.apply(authorityInfo.authority().asString())) - .map(a -> new InetSocketAddress(a, port)) - .collect(Collectors.toList()); + if (!route.authorized(authorization)) + { + continue; + } - for (InetSocketAddress authority : authorities) + Array32FW infos = beginEx.infos(); + ProxyInfoFW authorityInfo = infos.matchFirst(i -> i.kind() == AUTHORITY); + if (authorityInfo != null && route.matchesExplicit(r -> r.authority != null)) { - if (route.matchesExplicit(authority)) + final List authorities = Arrays + .stream(resolveHost(authorityInfo.authority().asString())) + .map(a -> new InetSocketAddress(a, port)) + .collect(Collectors.toList()); + + for (InetSocketAddress authority : authorities) { - resolved = authority; - break; + if (route.matchesExplicit(authority)) + { + resolved = authority; + break; + } } } - } - if (resolved == null) - { - resolved = resolve(address, authorization, route::matchesExplicit); - } + if (resolved == null) + { + resolved = resolve(address, authorization, route::matchesExplicit); + } - if (resolved != null) - { - break; + if (resolved != null) + { + break; + } } - } - - if (resolved == null && - options != null && - options.host != null && - !"*".equals(options.host)) - { - final List host = Arrays - .stream(resolveHost.apply(options.host)) - .map(a -> new InetSocketAddress(a, port)) - .collect(Collectors.toList()); - for (TcpRouteConfig route : binding.routes) + if (resolved == null && + options != null && + options.host != null && + !"*".equals(options.host)) { - if (!route.authorized(authorization)) + final List host = Arrays + .stream(resolveHost(options.host)) + .map(a -> new InetSocketAddress(a, port)) + .collect(Collectors.toList()); + + for (TcpRouteConfig route : binding.routes) { - continue; - } + if (!route.authorized(authorization)) + { + continue; + } - resolved = resolve(address, authorization, host::contains); + resolved = resolve(address, authorization, host::contains); - if (resolved != null) - { - break; + if (resolved != null) + { + break; + } } } } } - + catch (TcpDnsFailedException ex) + { + event.dnsResolutionFailed(traceId, binding.id, ex.hostname); + } return resolved; } + private InetAddress[] resolveHost( + String hostname) + { + try + { + return resolveHost.apply(hostname); + } + catch (Throwable ex) + { + throw new TcpDnsFailedException(ex, hostname); + } + } + public void detach( long bindingId) { @@ -291,4 +315,17 @@ private InetSocketAddress resolveInetSocketAddress( return resolved; } + + private static final class TcpDnsFailedException extends RuntimeException + { + private final String hostname; + + TcpDnsFailedException( + Throwable cause, + String hostname) + { + super(cause); + this.hostname = hostname; + } + } } diff --git a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java index cd69d87413..5373187431 100644 --- a/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java +++ b/runtime/binding-tcp/src/test/java/io/aklivity/zilla/runtime/binding/tcp/internal/streams/ClientIT.java @@ -21,7 +21,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.rules.RuleChain.outerRule; +import java.net.InetAddress; import java.net.InetSocketAddress; +import java.net.UnknownHostException; import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; @@ -37,6 +39,7 @@ import io.aklivity.zilla.runtime.engine.test.EngineRule; import io.aklivity.zilla.runtime.engine.test.annotation.Configuration; +import io.aklivity.zilla.runtime.engine.test.annotation.Configure; public class ClientIT { @@ -215,6 +218,18 @@ public void connnectionFailed() throws Exception k3po.finish(); } + @Test + @Configuration("client.host.yaml") + @Specification({ + "${app}/connection.failed/client" + }) + @Configure(name = "zilla.engine.host.resolver", + value = "io.aklivity.zilla.runtime.binding.tcp.internal.streams.ClientIT::resolveHost") + public void dnsResolutionFailed() throws Exception + { + k3po.finish(); + } + @Test @Configuration("client.host.yaml") @Specification({ @@ -326,4 +341,10 @@ public void shouldWriteDataAfterReceiveEnd() throws Exception } } } + + public static InetAddress[] resolveHost( + String host) throws UnknownHostException + { + throw new UnknownHostException(); + } } diff --git a/runtime/binding-tls/pom.xml b/runtime/binding-tls/pom.xml index 51f9dd5195..69930cae90 100644 --- a/runtime/binding-tls/pom.xml +++ b/runtime/binding-tls/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml @@ -26,7 +26,7 @@ 11 11 - 0.76 + 0.75 0 @@ -109,7 +109,7 @@ flyweight-maven-plugin ${project.version} - core proxy protocol + core proxy tls protocol io.aklivity.zilla.runtime.binding.tls.internal.types diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java new file mode 100644 index 0000000000..22722224ea --- /dev/null +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/TlsEventContext.java @@ -0,0 +1,120 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.binding.tls.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.binding.tls.internal.types.event.TlsEventFW; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class TlsEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final TlsEventFW.Builder tlsEventRW = new TlsEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int tlsTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public TlsEventContext( + EngineContext context) + { + this.tlsTypeId = context.supplyTypeId(TlsBinding.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void tlsFailed( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsProtocolRejected( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsProtocolRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsKeyRejected( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsKeyRejected(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsPeerNotVerified( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsPeerNotVerified(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } + + public void tlsHandshakeFailed( + long traceId, + long bindingId) + { + TlsEventFW event = tlsEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .tlsHandshakeFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + ) + .build(); + eventWriter.accept(tlsTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java index fd022f46d0..b111b35b77 100644 --- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsClientFactory.java @@ -37,6 +37,10 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import org.agrona.DirectBuffer; import org.agrona.MutableDirectBuffer; @@ -44,6 +48,7 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration; +import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig; import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW; @@ -129,6 +134,7 @@ public final class TlsClientFactory implements TlsStreamFactory private final LongUnaryOperator supplyReplyId; private final int initialPadAdjust; private final Long2ObjectHashMap bindings; + private final TlsEventContext event; private final int decodeMax; private final int handshakeMax; @@ -168,6 +174,7 @@ public TlsClientFactory( this.initialPadAdjust = Math.max(context.bufferPool().slotCapacity() >> 14, 1) * MAXIMUM_HEADER_SIZE; this.bindings = new Long2ObjectHashMap<>(); + this.event = new TlsEventContext(context); this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity()); this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer); this.outNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity() << 1); @@ -568,51 +575,80 @@ private int decodeNotHandshaking( try { - final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesProduced = result.bytesProduced(); - final int bytesConsumed = result.bytesConsumed(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesProduced = result.bytesProduced(); + final int bytesConsumed = result.bytesConsumed(); + + switch (result.getStatus()) { - if (!client.stream.isPresent()) + case BUFFER_UNDERFLOW: + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) { - client.onDecodeHandshakeFinished(traceId, budgetId); + if (!client.stream.isPresent()) + { + client.onDecodeHandshakeFinished(traceId, budgetId); + } } - } - if (bytesProduced == 0) - { - client.decoder = decodeHandshake; - progress += bytesConsumed; - } - else - { - assert bytesConsumed == tlsRecordBytes; - assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed); + if (bytesProduced == 0) + { + client.decoder = decodeHandshake; + progress += bytesConsumed; + } + else + { + assert bytesConsumed == tlsRecordBytes; + assert bytesProduced <= bytesConsumed : + String.format("%d <= %d", bytesProduced, bytesConsumed); - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); - client.decodableRecordBytes -= bytesConsumed; - assert client.decodableRecordBytes == 0; + client.decodableRecordBytes -= bytesConsumed; + assert client.decodableRecordBytes == 0; - client.decoder = decodeNotHandshakingUnwrapped; + client.decoder = decodeNotHandshakingUnwrapped; + } + break; + case CLOSED: + assert bytesProduced == 0; + client.onDecodeInboundClosed(traceId); + client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake; + progress += bytesConsumed; + break; } - break; - case CLOSED: - assert bytesProduced == 0; - client.onDecodeInboundClosed(traceId); - client.decoder = TlsState.replyClosed(client.state) ? decodeIgnoreAll : decodeHandshake; - progress += bytesConsumed; - break; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; } } catch (SSLException ex) @@ -750,37 +786,65 @@ private int decodeHandshakeNeedUnwrap( try { - final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesConsumed = result.bytesConsumed(); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - if (TlsState.replyClosed(client.state)) + final SSLEngineResult result = client.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesConsumed = result.bytesConsumed(); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { + case BUFFER_UNDERFLOW: + if (TlsState.replyClosed(client.state)) + { + client.decoder = decodeIgnoreAll; + } + break; + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + assert bytesProduced == 0; + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + { + client.onDecodeHandshakeFinished(traceId, budgetId); + } + client.decoder = decodeHandshake; + break; + case CLOSED: + assert bytesProduced == 0; + client.onDecodeInboundClosed(traceId); client.decoder = decodeIgnoreAll; + break; } - break; - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - assert bytesProduced == 0; - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) - { - client.onDecodeHandshakeFinished(traceId, budgetId); - } - client.decoder = decodeHandshake; - break; - case CLOSED: - assert bytesProduced == 0; - client.onDecodeInboundClosed(traceId); - client.decoder = decodeIgnoreAll; - break; - } - progress += bytesConsumed; + progress += bytesConsumed; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { @@ -1631,6 +1695,7 @@ private void onNetSignalHandshakeTimeout( final long traceId = signal.traceId(); cleanupNet(traceId); + event.tlsHandshakeFailed(traceId, client.originId); decoder = decodeIgnoreAll; } } @@ -1647,7 +1712,35 @@ private void doNetBegin( try { - tlsEngine.beginHandshake(); + try + { + tlsEngine.beginHandshake(); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { @@ -1998,38 +2091,66 @@ private void doEncodeWrap( try { - loop: - do + try { - final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + loop: + do { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - assert false; - break; - case CLOSED: - assert bytesProduced > 0; - doAppReset(traceId); - state = TlsState.closingReply(state); - break loop; - case OK: - assert bytesProduced > 0 || tlsEngine.isInboundDone(); - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { - if (proactiveReplyBegin) + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + assert false; + break; + case CLOSED: + assert bytesProduced > 0; + doAppReset(traceId); + state = TlsState.closingReply(state); + break loop; + case OK: + assert bytesProduced > 0 || tlsEngine.isInboundDone(); + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) { - onDecodeHandshakeFinished(traceId, budgetId); + if (proactiveReplyBegin) + { + onDecodeHandshakeFinished(traceId, budgetId); + } } + break; } - break; - } - } while (inAppByteBuffer.hasRemaining()); + } while (inAppByteBuffer.hasRemaining()); - final int outNetBytesProduced = outNetByteBuffer.position(); - doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + final int outNetBytesProduced = outNetByteBuffer.position(); + doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, client.originId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, client.originId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, client.originId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, client.originId); + throw ex; + } + catch (SSLException ex) + { + event.tlsFailed(traceId, client.originId); + throw ex; + } } catch (SSLException ex) { diff --git a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java index f78ccc88fb..91d30e9b3d 100644 --- a/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java +++ b/runtime/binding-tls/src/main/java/io/aklivity/zilla/runtime/binding/tls/internal/stream/TlsServerFactory.java @@ -42,7 +42,10 @@ import javax.net.ssl.SSLEngineResult; import javax.net.ssl.SSLEngineResult.HandshakeStatus; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLKeyException; import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLProtocolException; import javax.net.ssl.SSLSession; import javax.security.auth.x500.X500Principal; @@ -52,6 +55,7 @@ import org.agrona.concurrent.UnsafeBuffer; import io.aklivity.zilla.runtime.binding.tls.internal.TlsConfiguration; +import io.aklivity.zilla.runtime.binding.tls.internal.TlsEventContext; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsBindingConfig; import io.aklivity.zilla.runtime.binding.tls.internal.config.TlsRouteConfig; import io.aklivity.zilla.runtime.binding.tls.internal.types.OctetsFW; @@ -145,6 +149,7 @@ public final class TlsServerFactory implements TlsStreamFactory private final LongUnaryOperator supplyReplyId; private final int replyPadAdjust; private final Long2ObjectHashMap bindings; + private final TlsEventContext event; private final int decodeMax; private final int handshakeMax; @@ -183,6 +188,7 @@ public TlsServerFactory( this.handshakeMax = Math.min(config.handshakeWindowBytes(), decodeMax); this.handshakeTimeoutMillis = SECONDS.toMillis(config.handshakeTimeout()); this.bindings = new Long2ObjectHashMap<>(); + this.event = new TlsEventContext(context); this.inNetByteBuffer = ByteBuffer.allocate(writeBuffer.capacity()); this.inNetBuffer = new UnsafeBuffer(inNetByteBuffer); @@ -523,8 +529,36 @@ private int decodeBeforeHandshake( { try { - server.tlsEngine.beginHandshake(); - server.decoder = decodeHandshake; + try + { + server.tlsEngine.beginHandshake(); + server.decoder = decodeHandshake; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; + } } catch (SSLException | RuntimeException ex) { @@ -605,43 +639,72 @@ private int decodeNotHandshaking( try { - final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesProduced = result.bytesProduced(); - final int bytesConsumed = result.bytesConsumed(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - if (bytesProduced == 0) + final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesProduced = result.bytesProduced(); + final int bytesConsumed = result.bytesConsumed(); + + switch (result.getStatus()) { - server.decoder = decodeHandshake; + case BUFFER_UNDERFLOW: + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + if (bytesProduced == 0) + { + server.decoder = decodeHandshake; + progress += bytesConsumed; + } + else + { + assert bytesConsumed == tlsRecordBytes; + assert bytesProduced <= bytesConsumed : + String.format("%d <= %d", bytesProduced, bytesConsumed); + + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); + + server.decodableRecordBytes -= bytesConsumed; + assert server.decodableRecordBytes == 0; + + server.decoder = decodeNotHandshakingUnwrapped; + } + break; + case CLOSED: + assert bytesProduced == 0; + server.onDecodeInboundClosed(traceId); + server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake; progress += bytesConsumed; + break; } - else - { - assert bytesConsumed == tlsRecordBytes; - assert bytesProduced <= bytesConsumed : String.format("%d <= %d", bytesProduced, bytesConsumed); - - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); - - server.decodableRecordBytes -= bytesConsumed; - assert server.decodableRecordBytes == 0; - - server.decoder = decodeNotHandshakingUnwrapped; - } - break; - case CLOSED: - assert bytesProduced == 0; - server.onDecodeInboundClosed(traceId); - server.decoder = TlsState.initialClosed(server.state) ? decodeIgnoreAll : decodeHandshake; - progress += bytesConsumed; - break; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; } } catch (SSLException | RuntimeException ex) @@ -779,55 +842,83 @@ private int decodeHandshakeNeedUnwrap( try { - final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); - final int bytesConsumed = result.bytesConsumed(); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + try { - case BUFFER_UNDERFLOW: - if (TlsState.initialClosed(server.state)) - { - server.decoder = decodeIgnoreAll; - } - break; - case BUFFER_OVERFLOW: - assert false; - break; - case OK: - final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); - if (handshakeStatus == HandshakeStatus.FINISHED) - { - server.onDecodeHandshakeFinished(traceId, budgetId); - } + final SSLEngineResult result = server.tlsEngine.unwrap(inNetByteBuffer, outAppByteBuffer); + final int bytesConsumed = result.bytesConsumed(); + final int bytesProduced = result.bytesProduced(); - if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED) - { - final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW - .wrap(buffer, progress, progress + bytesConsumed) - .build(); - final int tlsRecordDataOffset = tlsRecordInfo.limit(); - final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length(); - - tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) - .payload(outAppBuffer, 0, bytesProduced) - .build(); - server.decoder = decodeNotHandshakingUnwrapped; - } - else + switch (result.getStatus()) { - server.decoder = decodeHandshake; + case BUFFER_UNDERFLOW: + if (TlsState.initialClosed(server.state)) + { + server.decoder = decodeIgnoreAll; + } + break; + case BUFFER_OVERFLOW: + assert false; + break; + case OK: + final HandshakeStatus handshakeStatus = result.getHandshakeStatus(); + if (handshakeStatus == HandshakeStatus.FINISHED) + { + server.onDecodeHandshakeFinished(traceId, budgetId); + } + + if (bytesProduced > 0 && handshakeStatus == HandshakeStatus.FINISHED) + { + final TlsRecordInfoFW tlsRecordInfo = tlsRecordInfoRW + .wrap(buffer, progress, progress + bytesConsumed) + .build(); + final int tlsRecordDataOffset = tlsRecordInfo.limit(); + final int tlsRecordDataLimit = tlsRecordDataOffset + tlsRecordInfo.length(); + + tlsUnwrappedDataRW.wrap(buffer, tlsRecordDataOffset, tlsRecordDataLimit) + .payload(outAppBuffer, 0, bytesProduced) + .build(); + server.decoder = decodeNotHandshakingUnwrapped; + } + else + { + server.decoder = decodeHandshake; + } + + break; + case CLOSED: + assert bytesProduced == 0; + server.onDecodeInboundClosed(traceId); + server.decoder = decodeIgnoreAll; + break; } - break; - case CLOSED: - assert bytesProduced == 0; - server.onDecodeInboundClosed(traceId); - server.decoder = decodeIgnoreAll; - break; + progress += bytesConsumed; + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, server.routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, server.routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, server.routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, server.routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, server.routedId); + throw ex; } - - progress += bytesConsumed; } catch (SSLException | RuntimeException ex) { @@ -1309,6 +1400,7 @@ private void onNetSignalHandshakeTimeout( handshakeTimeoutFutureId = NO_CANCEL_ID; cleanupNet(traceId); + event.tlsHandshakeFailed(traceId, routedId); decoder = decodeIgnoreAll; } } @@ -1652,36 +1744,64 @@ private void doEncodeWrap( try { - loop: - do + try { - final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); - final int bytesProduced = result.bytesProduced(); - - switch (result.getStatus()) + loop: + do { - case BUFFER_OVERFLOW: - case BUFFER_UNDERFLOW: - assert false; - break; - case CLOSED: - assert bytesProduced > 0; - assert tlsEngine.isOutboundDone(); - stream.ifPresent(s -> s.doAppResetLater(traceId)); - state = TlsState.closingReply(state); - break loop; - case OK: - assert bytesProduced > 0 || tlsEngine.isInboundDone(); - if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + final SSLEngineResult result = tlsEngine.wrap(inAppByteBuffer, outNetByteBuffer); + final int bytesProduced = result.bytesProduced(); + + switch (result.getStatus()) { - onDecodeHandshakeFinished(traceId, budgetId); + case BUFFER_OVERFLOW: + case BUFFER_UNDERFLOW: + assert false; + break; + case CLOSED: + assert bytesProduced > 0; + assert tlsEngine.isOutboundDone(); + stream.ifPresent(s -> s.doAppResetLater(traceId)); + state = TlsState.closingReply(state); + break loop; + case OK: + assert bytesProduced > 0 || tlsEngine.isInboundDone(); + if (result.getHandshakeStatus() == HandshakeStatus.FINISHED) + { + onDecodeHandshakeFinished(traceId, budgetId); + } + break; } - break; - } - } while (inAppByteBuffer.hasRemaining()); + } while (inAppByteBuffer.hasRemaining()); - final int outNetBytesProduced = outNetByteBuffer.position(); - doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + final int outNetBytesProduced = outNetByteBuffer.position(); + doNetData(traceId, budgetId, outNetBuffer, 0, outNetBytesProduced); + } + catch (SSLProtocolException ex) + { + event.tlsProtocolRejected(traceId, routedId); + throw ex; + } + catch (SSLKeyException ex) + { + event.tlsKeyRejected(traceId, routedId); + throw ex; + } + catch (SSLPeerUnverifiedException ex) + { + event.tlsPeerNotVerified(traceId, routedId); + throw ex; + } + catch (SSLHandshakeException ex) + { + event.tlsHandshakeFailed(traceId, routedId); + throw ex; + } + catch (SSLException | RuntimeException ex) + { + event.tlsFailed(traceId, routedId); + throw ex; + } } catch (SSLException | RuntimeException ex) { diff --git a/runtime/binding-ws/pom.xml b/runtime/binding-ws/pom.xml index 2f5b508823..65d1a3b785 100644 --- a/runtime/binding-ws/pom.xml +++ b/runtime/binding-ws/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/command-metrics/pom.xml b/runtime/command-metrics/pom.xml index c0c5fa2845..402371b5c9 100644 --- a/runtime/command-metrics/pom.xml +++ b/runtime/command-metrics/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/command-start/pom.xml b/runtime/command-start/pom.xml index fccd95b3ae..2984880454 100644 --- a/runtime/command-start/pom.xml +++ b/runtime/command-start/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/command-stop/pom.xml b/runtime/command-stop/pom.xml index c7b72bf133..5cb9763d9e 100644 --- a/runtime/command-stop/pom.xml +++ b/runtime/command-stop/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/command/pom.xml b/runtime/command/pom.xml index 413ba26357..72e8a3d401 100644 --- a/runtime/command/pom.xml +++ b/runtime/command/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/common/pom.xml b/runtime/common/pom.xml index 55330e9041..c979684c52 100644 --- a/runtime/common/pom.xml +++ b/runtime/common/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/engine/pom.xml b/runtime/engine/pom.xml index 07d3380776..952bbe989d 100644 --- a/runtime/engine/pom.xml +++ b/runtime/engine/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java index 7a8f6935a8..b89c0bde13 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/Engine.java @@ -57,6 +57,8 @@ import org.agrona.concurrent.AgentRunner; import io.aklivity.zilla.runtime.engine.binding.Binding; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.catalog.Catalog; import io.aklivity.zilla.runtime.engine.config.KindConfig; import io.aklivity.zilla.runtime.engine.exporter.Exporter; @@ -71,6 +73,7 @@ import io.aklivity.zilla.runtime.engine.internal.registry.FileWatcherTask; import io.aklivity.zilla.runtime.engine.internal.registry.HttpWatcherTask; import io.aklivity.zilla.runtime.engine.internal.registry.WatcherTask; +import io.aklivity.zilla.runtime.engine.internal.types.event.EventFW; import io.aklivity.zilla.runtime.engine.metrics.Collector; import io.aklivity.zilla.runtime.engine.metrics.MetricGroup; import io.aklivity.zilla.runtime.engine.model.Model; @@ -160,7 +163,7 @@ public final class Engine implements Collector, AutoCloseable EngineWorker worker = new EngineWorker(config, tasks, labels, errorHandler, tuning::affinity, bindings, exporters, guards, vaults, catalogs, models, metricGroups, - this, coreIndex, readonly); + this, this::supplyEventReader, coreIndex, readonly); workers.add(worker); } this.workers = workers; @@ -187,8 +190,20 @@ public final class Engine implements Collector, AutoCloseable final Map guardsByType = guards.stream() .collect(Collectors.toMap(g -> g.name(), g -> g)); - EngineManager manager = new EngineManager(schemaTypes, bindingsByType::get, guardsByType::get, - labels::supplyLabelId, maxWorkers, tuning, workers, logger, context, config, extensions, this::readURL); + EngineManager manager = new EngineManager( + schemaTypes, + bindingsByType::get, + guardsByType::get, + labels::supplyLabelId, + labels::lookupLabel, + maxWorkers, + tuning, + workers, + logger, + context, + config, + extensions, + this::readURL); this.configURL = config.configURL(); String protocol = configURL.getProtocol(); @@ -481,6 +496,11 @@ public long[][] histogramIds() return worker.histogramIds(); } + public MessageReader supplyEventReader() + { + return new EventReader(); + } + public String supplyLocalName( long namespacedId) { @@ -495,6 +515,48 @@ public int supplyLabelId( return worker.supplyTypeId(label); } + private final class EventReader implements MessageReader + { + private final EventFW eventRO = new EventFW(); + private int minWorkerIndex; + private long minTimeStamp; + + @Override + public int read( + MessageConsumer handler, + int messageCountLimit) + { + int messagesRead = 0; + boolean empty = false; + while (!empty && messagesRead < messageCountLimit) + { + int eventCount = 0; + minWorkerIndex = 0; + minTimeStamp = Long.MAX_VALUE; + for (int j = 0; j < workers.size(); j++) + { + final int workerIndex = j; + int eventPeeked = workers.get(workerIndex).peekEvent((m, b, i, l) -> + { + eventRO.wrap(b, i, i + l); + if (eventRO.timestamp() < minTimeStamp) + { + minTimeStamp = eventRO.timestamp(); + minWorkerIndex = workerIndex; + } + }); + eventCount += eventPeeked; + } + empty = eventCount == 0; + if (!empty) + { + messagesRead += workers.get(minWorkerIndex).readEvent(handler, 1); + } + } + return messagesRead; + } + } + // visible for testing public final class ContextImpl implements EngineExtContext { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java index 8ef1e2723a..4cd79e9eed 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineConfiguration.java @@ -52,6 +52,7 @@ public class EngineConfiguration extends Configuration public static final IntPropertyDef ENGINE_BUFFER_POOL_CAPACITY; public static final IntPropertyDef ENGINE_BUFFER_SLOT_CAPACITY; public static final IntPropertyDef ENGINE_STREAMS_BUFFER_CAPACITY; + public static final IntPropertyDef ENGINE_EVENTS_BUFFER_CAPACITY; public static final IntPropertyDef ENGINE_COUNTERS_BUFFER_CAPACITY; public static final IntPropertyDef ENGINE_BUDGETS_BUFFER_CAPACITY; public static final BooleanPropertyDef ENGINE_TIMESTAMPS; @@ -88,6 +89,8 @@ public class EngineConfiguration extends Configuration ENGINE_BUFFER_SLOT_CAPACITY = config.property("buffer.slot.capacity", 64 * 1024); ENGINE_STREAMS_BUFFER_CAPACITY = config.property("streams.buffer.capacity", EngineConfiguration::defaultStreamsBufferCapacity); + ENGINE_EVENTS_BUFFER_CAPACITY = config.property("events.buffer.capacity", + EngineConfiguration::defaultEventsBufferCapacity); ENGINE_BUDGETS_BUFFER_CAPACITY = config.property("budgets.buffer.capacity", EngineConfiguration::defaultBudgetsBufferCapacity); ENGINE_COUNTERS_BUFFER_CAPACITY = config.property("counters.buffer.capacity", 1024 * 1024); @@ -179,6 +182,11 @@ public int streamsBufferCapacity() return ENGINE_STREAMS_BUFFER_CAPACITY.getAsInt(this); } + public int eventsBufferCapacity() + { + return ENGINE_EVENTS_BUFFER_CAPACITY.getAsInt(this); + } + public int countersBufferCapacity() { return ENGINE_COUNTERS_BUFFER_CAPACITY.getAsInt(this); @@ -276,6 +284,12 @@ private static int defaultStreamsBufferCapacity( return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config); } + private static int defaultEventsBufferCapacity( + Configuration config) + { + return ENGINE_BUFFER_SLOT_CAPACITY.get(config) * ENGINE_WORKER_CAPACITY.getAsInt(config); + } + private static int defaultBudgetsBufferCapacity( Configuration config) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java index 2f113352a8..0360890834 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/EngineContext.java @@ -18,12 +18,14 @@ import java.net.InetAddress; import java.net.URL; import java.nio.channels.SelectableChannel; +import java.time.Clock; import java.util.function.LongSupplier; import org.agrona.MutableDirectBuffer; import io.aklivity.zilla.runtime.engine.binding.BindingHandler; import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor; import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor; import io.aklivity.zilla.runtime.engine.buffer.BufferPool; @@ -117,6 +119,9 @@ String supplyNamespace( String supplyLocalName( long namespacedId); + String supplyQName( + long namespacedId); + BindingHandler streamFactory(); GuardHandler supplyGuard( @@ -148,4 +153,10 @@ void onExporterAttached( void onExporterDetached( long exporterId); + + MessageConsumer supplyEventWriter(); + + MessageReader supplyEventReader(); + + Clock clock(); } diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java new file mode 100644 index 0000000000..9759b940d5 --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/binding/function/MessageReader.java @@ -0,0 +1,23 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.binding.function; + +public interface MessageReader +{ + int read( + MessageConsumer handler, + int messageCountLimit); +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java index b9e2132e18..0011a4a587 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/BindingConfig.java @@ -29,6 +29,7 @@ public class BindingConfig public transient ToLongFunction resolveId; public transient long vaultId; + public transient String qvault; public transient long[] metricIds; diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java index c22bed5226..d909b507d9 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/ConfigException.java @@ -24,4 +24,11 @@ public ConfigException( { super(message); } + + public ConfigException( + String message, + Throwable cause) + { + super(message, cause); + } } diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java new file mode 100644 index 0000000000..670b8e29de --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotator.java @@ -0,0 +1,160 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.config; + +import java.util.LinkedList; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonArrayBuilder; +import jakarta.json.JsonObject; +import jakarta.json.JsonObjectBuilder; +import jakarta.json.JsonValue; + +import org.agrona.collections.MutableInteger; + +public final class EngineConfigAnnotator +{ + private final LinkedList schemaKeys; + private final LinkedList schemaIndexes; + + public EngineConfigAnnotator() + { + this.schemaKeys = new LinkedList<>(); + this.schemaIndexes = new LinkedList<>(); + } + + public JsonObject annotate( + JsonObject jsonObject) + { + schemaKeys.clear(); + schemaIndexes.clear(); + + return (JsonObject) annotateJsonObject(jsonObject); + } + + private JsonValue annotateJsonObject( + JsonObject jsonObject) + { + JsonObjectBuilder builder = Json.createObjectBuilder(); + + jsonObject.forEach((key, value) -> + { + schemaKeys.push(key); + + parse: + if ("expression".equals(key)) + { + builder.add(key, value); + } + else if (value.getValueType() == JsonValue.ValueType.OBJECT) + { + builder.add(key, annotateJsonObject(value.asJsonObject())); + } + else if (value.getValueType() == JsonValue.ValueType.ARRAY) + { + if (jsonObject.containsKey("type") && key.equals("enum")) + { + break parse; + } + builder.add(key, annotateJsonArray(value.asJsonArray())); + } + else if (key.equals("type") && isPrimitiveType(value)) + { + builder.add("anyOf", createAnyOfTypes(jsonObject)); + } + else if (jsonObject.containsKey("type") && + isPrimitiveType(jsonObject.get("type"))) + { + if (key.equals("title") || key.equals("description")) + { + builder.add(key, value); + } + } + else + { + builder.add(key, value); + } + + schemaKeys.pop(); + }); + + return builder.build(); + } + + private JsonValue annotateJsonArray( + JsonArray jsonArray) + { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + MutableInteger index = new MutableInteger(); + + jsonArray.forEach(item -> + { + schemaIndexes.push(index.value++); + if (item.getValueType() == JsonValue.ValueType.OBJECT) + { + arrayBuilder.add(annotateJsonObject(item.asJsonObject())); + } + else + { + arrayBuilder.add(item); + } + schemaIndexes.pop(); + }); + + return arrayBuilder.build(); + } + + private boolean isPrimitiveType( + JsonValue type) + { + String typeText = type.toString().replaceAll("\"", ""); + return "string".equals(typeText) || + "integer".equals(typeText) || + "boolean".equals(typeText) || + "number".equals(typeText); + } + + private JsonArray createAnyOfTypes( + JsonObject properties) + { + JsonArrayBuilder anyOfArrayBuilder = Json.createArrayBuilder(); + JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); + + properties.forEach((key, value) -> + { + if (!"title".equals(key) && !"description".equals(key)) + { + objectBuilder.add(key, value); + } + }); + + anyOfArrayBuilder.add(objectBuilder); + + if (!(!schemaKeys.isEmpty() && + schemaKeys.size() > 1 && + "oneOf".equals(schemaKeys.get(1))) || + schemaIndexes.peek() == 0) + { + anyOfArrayBuilder.add(Json.createObjectBuilder() + .add("$ref", "#/$defs/expression") + ); + } + + return anyOfArrayBuilder.build(); + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java index 3c047b6435..a70c9c7bbe 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/EngineConfigReader.java @@ -30,14 +30,10 @@ import java.util.List; import java.util.function.Consumer; -import jakarta.json.Json; import jakarta.json.JsonArray; -import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonObject; -import jakarta.json.JsonObjectBuilder; import jakarta.json.JsonPatch; import jakarta.json.JsonReader; -import jakarta.json.JsonValue; import jakarta.json.bind.Jsonb; import jakarta.json.bind.JsonbBuilder; import jakarta.json.bind.JsonbConfig; @@ -62,6 +58,7 @@ public final class EngineConfigReader private final Collection schemaTypes; private final Consumer logger; + public EngineConfigReader( ConfigAdapterContext context, Resolver expressions, @@ -190,7 +187,8 @@ private boolean validateAnnotatedSchema( validate: try { - final JsonObject annotatedSchemaObject = (JsonObject) annotateJsonObject(schemaObject); + final EngineConfigAnnotator annotator = new EngineConfigAnnotator(); + final JsonObject annotatedSchemaObject = annotator.annotate(schemaObject); if (logger != null) { @@ -249,87 +247,5 @@ private boolean validateAnnotatedSchema( } - private JsonValue annotateJsonObject( - JsonObject jsonObject) - { - JsonObjectBuilder builder = Json.createObjectBuilder(); - - jsonObject.forEach((key, value) -> - { - if ("expression".equals(key)) - { - builder.add(key, value); - } - else if (value.getValueType() == JsonValue.ValueType.OBJECT) - { - builder.add(key, annotateJsonObject(value.asJsonObject())); - } - else if (value.getValueType() == JsonValue.ValueType.ARRAY) - { - builder.add(key, annotateJsonArray(value.asJsonArray())); - } - else if (key.equals("type") && - isPrimitiveType(value.toString().replaceAll("\"", ""))) - { - JsonValue pattern = jsonObject.get("pattern"); - builder.add(key, value); - builder.add("anyOf", createOneOfTypes(value.toString().replaceAll("\"", ""), pattern)); - } - else if (!"pattern".equals(key)) - { - builder.add(key, value); - } - }); - - return builder.build(); - } - - private JsonValue annotateJsonArray( - JsonArray jsonArray) - { - JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); - jsonArray.forEach(item -> - { - if (item.getValueType() == JsonValue.ValueType.OBJECT) - { - arrayBuilder.add(annotateJsonObject(item.asJsonObject())); - } - else - { - arrayBuilder.add(item); - } - }); - - return arrayBuilder.build(); - } - - private boolean isPrimitiveType( - String type) - { - return "string".equals(type) || - "integer".equals(type) || - "boolean".equals(type) || - "number".equals(type); - } - - private JsonArray createOneOfTypes( - String originalType, - JsonValue pattern) - { - JsonArrayBuilder oneOfArrayBuilder = Json.createArrayBuilder(); - JsonObjectBuilder objectBuilder = Json.createObjectBuilder(); - objectBuilder.add("type", originalType); - if (pattern != null) - { - objectBuilder.add("pattern", pattern); - } - oneOfArrayBuilder.add(objectBuilder); - - oneOfArrayBuilder.add(Json.createObjectBuilder() - .add("$ref", "#/$defs/expression") - ); - - return oneOfArrayBuilder.build(); - } } diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsAdapter.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/OptionsConfigAdapter.java similarity index 84% rename from runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsAdapter.java rename to runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/OptionsConfigAdapter.java index f568acdd32..20bfebc446 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsAdapter.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/config/OptionsConfigAdapter.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.config; +package io.aklivity.zilla.runtime.engine.config; import static java.util.function.Function.identity; import static java.util.stream.Collectors.toMap; @@ -25,18 +25,14 @@ import jakarta.json.JsonObject; import jakarta.json.bind.adapter.JsonbAdapter; -import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; -import io.aklivity.zilla.runtime.engine.config.OptionsConfig; -import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; - -public class OptionsAdapter implements JsonbAdapter +public class OptionsConfigAdapter implements JsonbAdapter { private final Map delegatesByName; private ConfigAdapterContext context; private OptionsConfigAdapterSpi delegate; - public OptionsAdapter( + public OptionsConfigAdapter( OptionsConfigAdapterSpi.Kind kind, ConfigAdapterContext context) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java index 54c3f372fc..a9da2772fe 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/guard/GuardHandler.java @@ -30,6 +30,8 @@ public interface GuardHandler * @return the session identifier */ long reauthorize( + long traceId, + long bindingId, long contextId, String credentials); diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java index 6cc76d2de7..74bba3d940 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/config/BindingConfigsAdapter.java @@ -40,6 +40,7 @@ import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder; import io.aklivity.zilla.runtime.engine.config.CompositeBindingAdapterSpi; import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; import io.aklivity.zilla.runtime.engine.config.RouteConfig; @@ -57,7 +58,7 @@ public class BindingConfigsAdapter implements JsonbAdapter @@ -32,14 +33,14 @@ public class ExporterAdapter implements JsonbAdapter bindingByType; private final Function guardByType; private final ToIntFunction supplyId; + private final IntFunction supplyName; private final IntFunction> maxWorkers; private final Tuning tuning; private final Collection dispatchers; @@ -89,6 +90,7 @@ public EngineManager( Function bindingByType, Function guardByType, ToIntFunction supplyId, + IntFunction supplyName, IntFunction> maxWorkers, Tuning tuning, Collection dispatchers, @@ -102,6 +104,7 @@ public EngineManager( this.bindingByType = bindingByType; this.guardByType = guardByType; this.supplyId = supplyId; + this.supplyName = supplyName; this.maxWorkers = maxWorkers; this.tuning = tuning; this.dispatchers = dispatchers; @@ -153,7 +156,7 @@ public EngineConfig reconfigure( if (current == null) { - throw new ConfigException("Engine configuration failed"); + throw new ConfigException("Engine configuration failed", ex); } } @@ -242,6 +245,7 @@ private void process( if (binding.vault != null) { binding.vaultId = resolver.resolve(binding.vault); + binding.qvault = resolver.format(binding.vaultId); } if (binding.catalogs != null) @@ -439,6 +443,14 @@ private long resolve( return id; } + + private String format( + long namespacedId) + { + return String.format("%s:%s", + supplyName.apply(NamespacedId.namespaceId(namespacedId)), + supplyName.apply(NamespacedId.localId(namespacedId))); + } } private static final class NamespaceConfigAdapterContext implements ConfigAdapterContext diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java index 63be6936fb..f07bfe1a2f 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/registry/EngineWorker.java @@ -42,6 +42,7 @@ import java.net.MalformedURLException; import java.net.URL; import java.nio.channels.SelectableChannel; +import java.time.Clock; import java.time.Duration; import java.util.BitSet; import java.util.Collection; @@ -60,6 +61,7 @@ import java.util.function.LongFunction; import java.util.function.LongSupplier; import java.util.function.LongUnaryOperator; +import java.util.function.Supplier; import org.agrona.DeadlineTimerWheel; import org.agrona.DeadlineTimerWheel.TimerHandler; @@ -86,6 +88,7 @@ import io.aklivity.zilla.runtime.engine.binding.BindingContext; import io.aklivity.zilla.runtime.engine.binding.BindingHandler; import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.budget.BudgetCreditor; import io.aklivity.zilla.runtime.engine.budget.BudgetDebitor; import io.aklivity.zilla.runtime.engine.buffer.BufferPool; @@ -108,6 +111,7 @@ import io.aklivity.zilla.runtime.engine.internal.exporter.ExporterAgent; import io.aklivity.zilla.runtime.engine.internal.layouts.BudgetsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.BufferPoolLayout; +import io.aklivity.zilla.runtime.engine.internal.layouts.EventsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.StreamsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout; import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout; @@ -210,6 +214,8 @@ public class EngineWorker implements EngineContext, Agent private final ScalarsLayout countersLayout; private final ScalarsLayout gaugesLayout; private final HistogramsLayout histogramsLayout; + private final EventsLayout eventsLayout; + private final Supplier supplyEventReader; private long initialId; private long promiseId; private long traceId; @@ -232,6 +238,7 @@ public EngineWorker( Collection models, Collection metricGroups, Collector collector, + Supplier supplyEventReader, int index, boolean readonly) { @@ -285,6 +292,11 @@ public EngineWorker( .readonly(readonly) .build(); + this.eventsLayout = new EventsLayout.Builder() + .path(config.directory().resolve(String.format("events%d", index))) + .capacity(config.eventsBufferCapacity()) + .build(); + this.agentName = String.format("engine/data#%d", index); this.streamsLayout = streamsLayout; this.bufferPoolLayout = bufferPoolLayout; @@ -407,6 +419,7 @@ public EngineWorker( this.idleStrategy = idleStrategy; this.errorHandler = errorHandler; this.exportersById = new Long2ObjectHashMap<>(); + this.supplyEventReader = supplyEventReader; } public static int indexOfId( @@ -441,6 +454,14 @@ public String supplyLocalName( return labels.lookupLabel(NamespacedId.localId(namespacedId)); } + @Override + public String supplyQName( + long namespacedId) + { + return String.format("%s.%s", labels.lookupLabel(NamespacedId.namespaceId(namespacedId)), + labels.lookupLabel(NamespacedId.localId(namespacedId))); + } + @Override public int supplyTypeId( String name) @@ -895,6 +916,18 @@ public LongConsumer supplyHistogramWriter( return histogramsLayout.supplyWriter(bindingId, metricId); } + @Override + public MessageConsumer supplyEventWriter() + { + return this.eventsLayout::writeEvent; + } + + @Override + public Clock clock() + { + return Clock.systemUTC(); + } + private void onSystemMessage( int msgTypeId, DirectBuffer buffer, @@ -1557,6 +1590,24 @@ public MessageConsumer supplyReceiver( return writersByIndex.computeIfAbsent(remoteIndex, supplyWriter); } + public int readEvent( + MessageConsumer handler, + int messageCountLimit) + { + return eventsLayout.readEvent(handler, messageCountLimit); + } + + public int peekEvent( + MessageConsumer handler) + { + return eventsLayout.peekEvent(handler); + } + + public MessageReader supplyEventReader() + { + return supplyEventReader.get(); + } + private MessageConsumer supplyWriter( int index) { diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java new file mode 100644 index 0000000000..4104b505ff --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/OneToOneRingBufferSpy.java @@ -0,0 +1,167 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.spy; + +import static org.agrona.BitUtil.align; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.ALIGNMENT; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.HEADER_LENGTH; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.lengthOffset; +import static org.agrona.concurrent.ringbuffer.RecordDescriptor.typeOffset; +import static org.agrona.concurrent.ringbuffer.RingBuffer.PADDING_MSG_TYPE_ID; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.HEAD_POSITION_OFFSET; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TAIL_POSITION_OFFSET; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.TRAILER_LENGTH; +import static org.agrona.concurrent.ringbuffer.RingBufferDescriptor.checkCapacity; + +import java.util.concurrent.atomic.AtomicLong; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.AtomicBuffer; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public class OneToOneRingBufferSpy implements RingBufferSpy +{ + private final int capacity; + private final AtomicLong spyPosition; + private final AtomicBuffer buffer; + + public OneToOneRingBufferSpy( + final AtomicBuffer buffer) + { + this.buffer = buffer; + checkCapacity(buffer.capacity()); + capacity = buffer.capacity() - TRAILER_LENGTH; + + buffer.verifyAlignment(); + + spyPosition = new AtomicLong(); + } + + @Override + public void spyAt( + SpyPosition position) + { + switch (position) + { + case ZERO: + spyPosition.lazySet(0); + break; + case HEAD: + spyPosition.lazySet(buffer.getLong(capacity + HEAD_POSITION_OFFSET)); + break; + case TAIL: + spyPosition.lazySet(buffer.getLong(capacity + TAIL_POSITION_OFFSET)); + break; + } + } + + @Override + public DirectBuffer buffer() + { + return buffer; + } + + @Override + public long producerPosition() + { + return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + TAIL_POSITION_OFFSET); + } + + @Override + public long consumerPosition() + { + return buffer.getLong(buffer.capacity() - TRAILER_LENGTH + HEAD_POSITION_OFFSET); + } + + @Override + public int spy( + final MessageConsumer handler) + { + return spy(handler, Integer.MAX_VALUE); + } + + @Override + public int spy( + final MessageConsumer handler, + final int messageCountLimit) + { + int messagesRead = 0; + + final AtomicBuffer buffer = this.buffer; + final long head = spyPosition.get(); + + int bytesRead = 0; + + final int capacity = this.capacity; + final int headIndex = (int)head & (capacity - 1); + final int contiguousBlockLength = capacity - headIndex; + + try + { + while (bytesRead < contiguousBlockLength && messagesRead < messageCountLimit) + { + final int recordIndex = headIndex + bytesRead; + final int recordLength = buffer.getIntVolatile(lengthOffset(recordIndex)); + if (recordLength <= 0) + { + break; + } + + bytesRead += align(recordLength, ALIGNMENT); + + final int messageTypeId = buffer.getInt(typeOffset(recordIndex)); + if (PADDING_MSG_TYPE_ID == messageTypeId) + { + continue; + } + + ++messagesRead; + handler.accept(messageTypeId, buffer, recordIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH); + } + } + finally + { + if (bytesRead != 0) + { + spyPosition.lazySet(head + bytesRead); + } + } + + return messagesRead; + } + + public int peek( + final MessageConsumer handler) + { + final AtomicBuffer buffer = this.buffer; + final long head = spyPosition.get(); + final int capacity = this.capacity; + final int headIndex = (int)head & (capacity - 1); + final int recordLength = buffer.getIntVolatile(lengthOffset(headIndex)); + int messagesPeeked = 0; + if (recordLength > 0) + { + final int messageTypeId = buffer.getInt(typeOffset(headIndex)); + if (PADDING_MSG_TYPE_ID != messageTypeId) + { + handler.accept(messageTypeId, buffer, headIndex + HEADER_LENGTH, recordLength - HEADER_LENGTH); + messagesPeeked = 1; + } + } + return messagesPeeked; + } +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java new file mode 100644 index 0000000000..a863b76d1e --- /dev/null +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/internal/spy/RingBufferSpy.java @@ -0,0 +1,41 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.spy; + +import org.agrona.DirectBuffer; + +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; + +public interface RingBufferSpy +{ + enum SpyPosition + { + ZERO, + HEAD, + TAIL + } + + void spyAt(SpyPosition position); + + int spy(MessageConsumer handler); + int spy(MessageConsumer handler, int messageCountLimit); + int peek(MessageConsumer handler); + + long producerPosition(); + long consumerPosition(); + + DirectBuffer buffer(); +} diff --git a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java index 7d6b742873..9a4be5e39e 100644 --- a/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java +++ b/runtime/engine/src/main/java/io/aklivity/zilla/runtime/engine/namespace/NamespacedId.java @@ -18,15 +18,15 @@ public final class NamespacedId { public static int namespaceId( - long bindingId) + long namespacedId) { - return (int)(bindingId >> Integer.SIZE) & 0xffff_ffff; + return (int)(namespacedId >> Integer.SIZE) & 0xffff_ffff; } public static int localId( - long bindingId) + long namespacedId) { - return (int)(bindingId >> 0) & 0xffff_ffff; + return (int)(namespacedId >> 0) & 0xffff_ffff; } public static long id( diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java new file mode 100644 index 0000000000..2b3d2fa42b --- /dev/null +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/config/EngineConfigAnnotatorTest.java @@ -0,0 +1,60 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.config; + +import static org.junit.Assert.assertTrue; + +import java.io.StringReader; + +import jakarta.json.Json; +import jakarta.json.JsonArray; +import jakarta.json.JsonObject; +import jakarta.json.JsonReader; + +import org.junit.Test; + +public class EngineConfigAnnotatorTest +{ + @Test + public void shouldAnnotateJsonSchema() + { + final String json = new String("{" + + " \"title\": \"Port\"," + + " \"oneOf\":" + + " [" + + " {" + + " \"type\": \"integer\"" + + " }," + + " {" + + " \"type\": \"string\"," + + " \"pattern\": \"^\\\\d+(-\\\\d+)?$\"" + + " }" + + " ]" + + "}"); + try (JsonReader jsonReader = Json.createReader(new StringReader(json))) + { + JsonObject jsonObject = jsonReader.readObject(); + EngineConfigAnnotator annotator = new EngineConfigAnnotator(); + final JsonObject annotated = annotator.annotate(jsonObject); + + JsonArray jsonArray = annotator.annotate(annotated).getJsonArray("oneOf"); + + assertTrue(jsonArray.get(0).asJsonObject().asJsonObject().getJsonArray("anyOf").size() == 2); + assertTrue(jsonArray.get(1).asJsonObject().asJsonObject().getJsonArray("anyOf").size() == 1); + + } + } +} diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java index 711dca56e2..4ae65aba4e 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/EngineTest.java @@ -34,6 +34,7 @@ import io.aklivity.zilla.runtime.engine.Engine; import io.aklivity.zilla.runtime.engine.EngineConfiguration; +import io.aklivity.zilla.runtime.engine.binding.function.MessageReader; import io.aklivity.zilla.runtime.engine.ext.EngineExtContext; import io.aklivity.zilla.runtime.engine.ext.EngineExtSpi; @@ -250,6 +251,30 @@ public void shouldNotConfigureUnknownScheme() throws Exception } } + @Test + public void shouldReadEvents() + { + List errors = new LinkedList<>(); + EngineConfiguration config = new EngineConfiguration(properties); + try (Engine engine = Engine.builder() + .config(config) + .errorHandler(errors::add) + .build()) + { + engine.start(); + MessageReader events = engine.supplyEventReader(); + events.read((m, b, i, l) -> {}, 1); + } + catch (Throwable ex) + { + errors.add(ex); + } + finally + { + assertThat(errors, empty()); + } + } + public static final class TestEngineExt implements EngineExtSpi { public static volatile CountDownLatch registerLatch = new CountDownLatch(1); diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsConfigAdapterTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsConfigAdapterTest.java index f7f2afaa45..d6fca2f7ec 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsConfigAdapterTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/config/OptionsConfigAdapterTest.java @@ -34,6 +34,7 @@ import io.aklivity.zilla.runtime.engine.config.ConfigAdapterContext; import io.aklivity.zilla.runtime.engine.config.OptionsConfig; +import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapter; import io.aklivity.zilla.runtime.engine.config.OptionsConfigAdapterSpi; import io.aklivity.zilla.runtime.engine.test.internal.binding.config.TestBindingOptionsConfig; @@ -45,13 +46,13 @@ public class OptionsConfigAdapterTest @Mock private ConfigAdapterContext context; - private OptionsAdapter adapter; + private OptionsConfigAdapter adapter; private Jsonb jsonb; @Before public void initJson() { - adapter = new OptionsAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); + adapter = new OptionsConfigAdapter(OptionsConfigAdapterSpi.Kind.BINDING, context); adapter.adaptType("test"); JsonbConfig config = new JsonbConfig() .withAdapters(adapter); diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java new file mode 100644 index 0000000000..a0aeda974b --- /dev/null +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/EventsLayoutTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.aklivity.zilla.runtime.engine.internal.layouts; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertThat; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.agrona.DirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; +import org.junit.Test; + +public class EventsLayoutTest +{ + private static final Path PATH = Paths.get("target/zilla-itests/events0"); + private static final int CAPACITY = 1024; + + private int msgTypeId; + + @Test + public void shouldWriteAndReadEvents() + { + // GIVEN + EventsLayout layout = new EventsLayout.Builder() + .path(PATH) + .capacity(CAPACITY) + .build(); + layout.writeEvent(42, new UnsafeBuffer(), 0, 0); + msgTypeId = 0; + + // WHEN + int count = layout.readEvent(this::readEvent, 1); + + // THEN + assertThat(count, equalTo(1)); + assertThat(msgTypeId, equalTo(42)); + } + + private void readEvent( + int msgTypeId, + DirectBuffer buffer, + int index, + int length) + { + this.msgTypeId = msgTypeId; + } +} diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java similarity index 98% rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java index 9109ab83c2..9d5d5070ae 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/HistogramsLayoutTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/HistogramsLayoutTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.layout.metrics; +package io.aklivity.zilla.runtime.engine.internal.layouts.metrics; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -28,8 +28,6 @@ import org.junit.Test; -import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.HistogramsLayout; - public class HistogramsLayoutTest { @Test diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java similarity index 96% rename from runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java rename to runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java index bbfdefa615..0a31ac6eb1 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layout/metrics/ScalarsLayoutTest.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/internal/layouts/metrics/ScalarsLayoutTest.java @@ -13,7 +13,7 @@ * License for the specific language governing permissions and limitations * under the License. */ -package io.aklivity.zilla.runtime.engine.internal.layout.metrics; +package io.aklivity.zilla.runtime.engine.internal.layouts.metrics; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; @@ -28,8 +28,6 @@ import org.junit.Test; -import io.aklivity.zilla.runtime.engine.internal.layouts.metrics.ScalarsLayout; - public class ScalarsLayoutTest { @Test diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java index 1e872dfcdd..ccc663209e 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/guard/TestGuardHandler.java @@ -50,6 +50,8 @@ public TestGuardHandler( @Override public long reauthorize( + long traceId, + long bindingId, long contextId, String credentials) { diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java index d31970af0c..cb53b75432 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddress.java @@ -28,38 +28,44 @@ public final class ZillaChannelAddress extends ChannelAddress private final long authorization; private final String namespace; private final String binding; + private final String ephemeralName; public ZillaChannelAddress( URI location, long authorization, - String namespace) + String namespace, + String ephemeral) { - this(location, authorization, namespace, bindingName(location)); + this(location, authorization, namespace, bindingName(location), ephemeral); } private ZillaChannelAddress( URI location, long authorization, String namespace, - String binding) + String binding, + String ephemeral) { super(location); this.authorization = authorization; this.namespace = requireNonNull(namespace); this.binding = requireNonNull(binding); + this.ephemeralName = requireNonNull(ephemeral); } private ZillaChannelAddress( URI location, ChannelAddress transport, boolean ephemeral, + String ephemeralName, long authorization, String namespace, String binding) { super(location, transport, ephemeral); + this.ephemeralName = ephemeralName; this.authorization = authorization; this.namespace = requireNonNull(namespace); this.binding = requireNonNull(binding); @@ -97,7 +103,7 @@ private ZillaChannelAddress newEphemeralAddress( URI location, ChannelAddress transport) { - return new ZillaChannelAddress(location, transport, true, authorization, "ephemeral", binding); + return new ZillaChannelAddress(location, transport, true, ephemeralName, authorization, ephemeralName, binding); } private static String bindingName( diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java index a800d682dd..04ad0df527 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/behavior/ZillaChannelAddressFactory.java @@ -18,6 +18,7 @@ import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_AUTHORIZATION; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_BUDGET_ID; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_BYTE_ORDER; +import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_EPHEMERAL; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_PADDING; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_REPLY_TO; import static io.aklivity.zilla.runtime.engine.test.internal.k3po.ext.types.ZillaTypeSystem.OPTION_STREAM_ID; @@ -72,7 +73,7 @@ protected ChannelAddress newChannelAddress0( } } - Collection> allOptionTypes = asList(OPTION_REPLY_TO, OPTION_WINDOW, OPTION_BUDGET_ID, + Collection> allOptionTypes = asList(OPTION_EPHEMERAL, OPTION_REPLY_TO, OPTION_WINDOW, OPTION_BUDGET_ID, OPTION_STREAM_ID, OPTION_PADDING, OPTION_UPDATE, OPTION_AUTHORIZATION, OPTION_THROTTLE, OPTION_TRANSMISSION, OPTION_BYTE_ORDER); for (TypeInfo optionType : allOptionTypes) @@ -90,7 +91,8 @@ protected ChannelAddress newChannelAddress0( final long authorization = (Long) options.getOrDefault(OPTION_AUTHORIZATION.getName(), 0L); final String replyTo = (String) options.getOrDefault(OPTION_REPLY_TO.getName(), "test"); + final String ephemeral = (String) options.getOrDefault(OPTION_EPHEMERAL.getName(), "ephemeral"); - return new ZillaChannelAddress(location, authorization, replyTo); + return new ZillaChannelAddress(location, authorization, replyTo, ephemeral); } } diff --git a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java index f7222b0834..d404938aef 100644 --- a/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java +++ b/runtime/engine/src/test/java/io/aklivity/zilla/runtime/engine/test/internal/k3po/ext/types/ZillaTypeSystem.java @@ -30,6 +30,7 @@ public final class ZillaTypeSystem implements TypeSystemSpi { public static final String NAME = "zilla"; + public static final TypeInfo OPTION_EPHEMERAL = new TypeInfo<>("ephemeral", String.class); public static final TypeInfo OPTION_REPLY_TO = new TypeInfo<>("replyTo", String.class); public static final TypeInfo OPTION_WINDOW = new TypeInfo<>("window", Integer.class); public static final TypeInfo OPTION_SHARED_WINDOW = new TypeInfo<>("sharedWindow", Integer.class); @@ -95,6 +96,7 @@ public ZillaTypeSystem() this.acceptOptions = unmodifiableSet(acceptOptions); Set> connectOptions = new LinkedHashSet<>(); + connectOptions.add(OPTION_EPHEMERAL); connectOptions.add(OPTION_REPLY_TO); connectOptions.add(OPTION_WINDOW); connectOptions.add(OPTION_SHARED_WINDOW); diff --git a/runtime/exporter-prometheus/pom.xml b/runtime/exporter-prometheus/pom.xml index f699f96e6d..71a9c59526 100644 --- a/runtime/exporter-prometheus/pom.xml +++ b/runtime/exporter-prometheus/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/guard-jwt/pom.xml b/runtime/guard-jwt/pom.xml index 08909b519e..414415cc0d 100644 --- a/runtime/guard-jwt/pom.xml +++ b/runtime/guard-jwt/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml @@ -103,6 +103,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core jwt + io.aklivity.zilla.runtime.guard.jwt.internal.types + + + + + generate + + + + com.mycila license-maven-plugin @@ -185,6 +201,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/runtime/guard/jwt/internal/types/**/*.class + BUNDLE diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java new file mode 100644 index 0000000000..99d0fa6146 --- /dev/null +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtEventContext.java @@ -0,0 +1,61 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +package io.aklivity.zilla.runtime.guard.jwt.internal; + +import java.nio.ByteBuffer; +import java.time.Clock; + +import org.agrona.MutableDirectBuffer; +import org.agrona.concurrent.UnsafeBuffer; + +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; +import io.aklivity.zilla.runtime.guard.jwt.internal.types.event.JwtEventFW; + +public class JwtEventContext +{ + private static final int EVENT_BUFFER_CAPACITY = 1024; + + private final JwtEventFW.Builder jwtEventRW = new JwtEventFW.Builder(); + private final MutableDirectBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY)); + private final int jwtTypeId; + private final MessageConsumer eventWriter; + private final Clock clock; + + public JwtEventContext( + EngineContext context) + { + this.jwtTypeId = context.supplyTypeId(JwtGuard.NAME); + this.eventWriter = context.supplyEventWriter(); + this.clock = context.clock(); + } + + public void authorizationFailed( + long traceId, + long bindingId, + String identity) + { + JwtEventFW event = jwtEventRW + .wrap(eventBuffer, 0, eventBuffer.capacity()) + .authorizationFailed(e -> e + .timestamp(clock.millis()) + .traceId(traceId) + .namespacedId(bindingId) + .identity(identity) + ) + .build(); + eventWriter.accept(jwtTypeId, event.buffer(), event.offset(), event.limit()); + } +} diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java index 1412213c25..55c141be78 100644 --- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardContext.java @@ -28,12 +28,14 @@ final class JwtGuardContext implements GuardContext { private final Long2ObjectHashMap handlersById; private final LongSupplier supplyAuthorizedId; + private final EngineContext context; JwtGuardContext( Configuration config, EngineContext context) { this.handlersById = new Long2ObjectHashMap<>(); + this.context = context; this.supplyAuthorizedId = context::supplyAuthorizedId; } @@ -42,7 +44,7 @@ public JwtGuardHandler attach( GuardConfig guard) { JwtOptionsConfig options = (JwtOptionsConfig) guard.options; - JwtGuardHandler handler = new JwtGuardHandler(options, supplyAuthorizedId, guard.readURL); + JwtGuardHandler handler = new JwtGuardHandler(options, context, supplyAuthorizedId, guard.readURL); handlersById.put(guard.id, handler); return handler; } diff --git a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java index 54f2358905..b34a2edd36 100644 --- a/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java +++ b/runtime/guard-jwt/src/main/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandler.java @@ -42,6 +42,7 @@ import org.jose4j.jwt.consumer.InvalidJwtException; import org.jose4j.lang.JoseException; +import io.aklivity.zilla.runtime.engine.EngineContext; import io.aklivity.zilla.runtime.engine.guard.GuardHandler; import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeyConfig; import io.aklivity.zilla.runtime.guard.jwt.config.JwtKeySetConfig; @@ -59,9 +60,11 @@ public class JwtGuardHandler implements GuardHandler private final Long2ObjectHashMap sessionsById; private final LongSupplier supplyAuthorizedId; private final Long2ObjectHashMap sessionStoresByContextId; + private final JwtEventContext event; public JwtGuardHandler( JwtOptionsConfig options, + EngineContext context, LongSupplier supplyAuthorizedId, Function readURL) { @@ -113,14 +116,18 @@ public JwtGuardHandler( this.supplyAuthorizedId = supplyAuthorizedId; this.sessionsById = new Long2ObjectHashMap<>(); this.sessionStoresByContextId = new Long2ObjectHashMap<>(); + this.event = new JwtEventContext(context); } @Override public long reauthorize( + long traceId, + long bindingId, long contextId, String credentials) { JwtSession session = null; + String subject = null; authorize: try @@ -147,6 +154,7 @@ public long reauthorize( String payload = signature.getPayload(); JwtClaims claims = JwtClaims.parse(payload); + subject = claims.getSubject(); NumericDate notBefore = claims.getNotBefore(); NumericDate notAfter = claims.getExpirationTime(); String issuer = claims.getIssuer(); @@ -161,7 +169,6 @@ public long reauthorize( break authorize; } - String subject = claims.getSubject(); List roles = Optional.ofNullable(claims.getClaimValue("scope")) .map(s -> s.toString().intern()) .map(s -> s.split("\\s+")) @@ -185,7 +192,10 @@ public long reauthorize( { // not authorized } - + if (session == null) + { + event.authorizationFailed(traceId, bindingId, subject); + } return session != null ? session.authorized : NOT_AUTHORIZED; } diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java index 88649c6a63..90bd4add79 100644 --- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java +++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardHandlerTest.java @@ -24,8 +24,11 @@ import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import java.security.KeyPair; +import java.time.Clock; import java.time.Duration; import java.time.Instant; import java.util.function.Function; @@ -34,14 +37,27 @@ import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.lang.JoseException; +import org.junit.Before; import org.junit.Test; +import io.aklivity.zilla.runtime.engine.EngineContext; +import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer; import io.aklivity.zilla.runtime.guard.jwt.config.JwtOptionsConfig; public class JwtGuardHandlerTest { private static final Function READ_KEYS_URL = url -> "{}"; + private EngineContext context; + + @Before + public void init() + { + context = mock(EngineContext.class); + when(context.clock()).thenReturn(mock(Clock.class)); + when(context.supplyEventWriter()).thenReturn(mock(MessageConsumer.class)); + } + @Test public void shouldAuthorize() throws Exception { @@ -53,7 +69,7 @@ public void shouldAuthorize() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -66,7 +82,7 @@ public void shouldAuthorize() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, not(equalTo(0L))); assertThat(guard.identity(sessionId), equalTo("testSubject")); @@ -86,7 +102,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -99,7 +115,7 @@ public void shouldChallengeDuringChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); } @@ -115,7 +131,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -127,7 +143,7 @@ public void shouldNotChallengeDuringWindowWithoutSubject() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); } @@ -143,7 +159,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -156,7 +172,7 @@ public void shouldNotChallengeBeforeChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertFalse(guard.challenge(sessionId, now.plusSeconds(5L).toEpochMilli())); } @@ -172,7 +188,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -185,7 +201,7 @@ public void shouldNotChallengeAgainDuringChallengeWindow() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertTrue(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); assertFalse(guard.challenge(sessionId, now.plusSeconds(8L).toEpochMilli())); @@ -201,7 +217,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -209,7 +225,7 @@ public void shouldNotAuthorizeWhenAlgorithmDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS512"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -223,7 +239,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -233,7 +249,7 @@ public void shouldNotAuthorizeWhenSignatureInvalid() throws Exception .replaceFirst("\\.[^X]", ".X") .replaceFirst("\\.[^Y]", ".Y"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -247,7 +263,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "not test issuer"); @@ -255,7 +271,7 @@ public void shouldNotAuthorizeWhenIssuerDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -269,7 +285,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -277,7 +293,7 @@ public void shouldNotAuthorizeWhenAudienceDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -291,7 +307,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -302,7 +318,7 @@ public void shouldNotAuthorizeWhenExpired() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -316,7 +332,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception .audience("testAudience") .key(RFC7515_RS256_CONFIG) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -327,7 +343,7 @@ public void shouldNotAuthorizeWhenNotYetValid() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, equalTo(0L)); } @@ -343,7 +359,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); JwtClaims claims = new JwtClaims(); claims.setClaim("iss", "test issuer"); @@ -352,7 +368,7 @@ public void shouldNotVerifyAuthorizedWhenRolesInsufficient() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); assertThat(sessionId, not(equalTo(0L))); assertFalse(guard.verify(sessionId, asList("read:stream", "write:stream"))); @@ -369,7 +385,7 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -382,12 +398,12 @@ public void shouldReauthorizeWhenExpirationLater() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis())); @@ -404,7 +420,7 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -417,13 +433,13 @@ public void shouldReauthorizeWhenScopeBroader() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); claims.setClaim("scope", "read:stream write:stream"); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 60L).toMillis())); @@ -440,7 +456,7 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -453,12 +469,12 @@ public void shouldNotReauthorizeWhenExpirationEarlier() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 5L); String tokenPlus5 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus5 = guard.reauthorize(101L, tokenPlus5); + long sessionIdPlus5 = guard.reauthorize(0L, 0L, 101L, tokenPlus5); assertThat(sessionIdPlus5, equalTo(sessionIdPlus10)); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -475,7 +491,7 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -488,13 +504,13 @@ public void shouldNotReauthorizeWhenScopeNarrower() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); claims.setClaim("scope", "read:stream"); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -512,7 +528,7 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -525,13 +541,13 @@ public void shouldNotReauthorizeWhenSubjectDiffers() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("sub", "otherSubject"); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(101L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 101L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -549,7 +565,7 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -562,12 +578,12 @@ public void shouldNotReauthorizeWhenContextDiffers() throws Exception String tokenPlus10 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus10 = guard.reauthorize(101L, tokenPlus10); + long sessionIdPlus10 = guard.reauthorize(0L, 0L, 101L, tokenPlus10); claims.setClaim("exp", now.getEpochSecond() + 60L); String tokenPlus60 = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionIdPlus60 = guard.reauthorize(202L, tokenPlus60); + long sessionIdPlus60 = guard.reauthorize(0L, 0L, 202L, tokenPlus60); assertThat(sessionIdPlus60, not(equalTo(sessionIdPlus10))); assertThat(guard.expiresAt(sessionIdPlus10), equalTo(ofSeconds(now.getEpochSecond() + 10L).toMillis())); @@ -585,7 +601,7 @@ public void shouldDeauthorize() throws Exception .key(RFC7515_RS256_CONFIG) .challenge(challenge) .build(); - JwtGuardHandler guard = new JwtGuardHandler(options, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); + JwtGuardHandler guard = new JwtGuardHandler(options, context, new MutableLong(1L)::getAndIncrement, READ_KEYS_URL); Instant now = Instant.now(); @@ -598,7 +614,7 @@ public void shouldDeauthorize() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = guard.reauthorize(101L, token); + long sessionId = guard.reauthorize(0L, 0L, 101L, token); guard.deauthorize(sessionId); } diff --git a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java index 9d54435ab1..1f41b642a8 100644 --- a/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java +++ b/runtime/guard-jwt/src/test/java/io/aklivity/zilla/runtime/guard/jwt/internal/JwtGuardTest.java @@ -167,7 +167,7 @@ public void shouldNotVerifyRolesWhenInsufficient() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long authorizedId = handler.reauthorize(101L, token); + long authorizedId = handler.reauthorize(0L, 0L, 101L, token); assertFalse(verifier.test(authorizedId)); } @@ -220,7 +220,7 @@ public void shouldVerifyRolesWhenExact() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -272,7 +272,7 @@ public void shouldVerifyRolesWhenSuperset() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -323,7 +323,7 @@ public void shouldVerifyRolesWhenEmpty() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -374,7 +374,7 @@ public void shouldVerifyWhenIndexDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertTrue(verifier.test(sessionId)); } @@ -425,7 +425,7 @@ public void shouldIdentify() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertEquals("testSubject", identifier.apply(sessionId)); } @@ -478,7 +478,7 @@ public void shouldIdentifyWhenIndexDiffers() throws Exception String token = sign(claims.toJson(), "test", RFC7515_RS256, "RS256"); - long sessionId = handler.reauthorize(101L, token); + long sessionId = handler.reauthorize(0L, 0L, 101L, token); assertEquals("testSubject", identifier.apply(sessionId)); } diff --git a/runtime/metrics-grpc/pom.xml b/runtime/metrics-grpc/pom.xml index 1f038792b9..d3270ea8a6 100644 --- a/runtime/metrics-grpc/pom.xml +++ b/runtime/metrics-grpc/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/metrics-http/pom.xml b/runtime/metrics-http/pom.xml index 4b556e296a..09e2e07a8d 100644 --- a/runtime/metrics-http/pom.xml +++ b/runtime/metrics-http/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/metrics-stream/pom.xml b/runtime/metrics-stream/pom.xml index 14bdf06a6b..9cf644501d 100644 --- a/runtime/metrics-stream/pom.xml +++ b/runtime/metrics-stream/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/pom.xml b/runtime/pom.xml index c8cebc32a0..dad679a4cb 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/resolver-env/pom.xml b/runtime/resolver-env/pom.xml index 2a1456511d..6f17107b41 100644 --- a/runtime/resolver-env/pom.xml +++ b/runtime/resolver-env/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/runtime/vault-filesystem/pom.xml b/runtime/vault-filesystem/pom.xml index aaad52dcec..15be96fab0 100644 --- a/runtime/vault-filesystem/pom.xml +++ b/runtime/vault-filesystem/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla runtime - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-echo.spec/pom.xml b/specs/binding-echo.spec/pom.xml index 7a00d7968d..64f61c8a02 100644 --- a/specs/binding-echo.spec/pom.xml +++ b/specs/binding-echo.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-fan.spec/pom.xml b/specs/binding-fan.spec/pom.xml index 1a6b6c2a1e..7818e78016 100644 --- a/specs/binding-fan.spec/pom.xml +++ b/specs/binding-fan.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-filesystem.spec/pom.xml b/specs/binding-filesystem.spec/pom.xml index 7d08f7368a..b24aac0741 100644 --- a/specs/binding-filesystem.spec/pom.xml +++ b/specs/binding-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-grpc-kafka.spec/pom.xml b/specs/binding-grpc-kafka.spec/pom.xml index ee30ad9d85..59ca15086a 100644 --- a/specs/binding-grpc-kafka.spec/pom.xml +++ b/specs/binding-grpc-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-grpc.spec/pom.xml b/specs/binding-grpc.spec/pom.xml index 9919385401..2c616de8fb 100644 --- a/specs/binding-grpc.spec/pom.xml +++ b/specs/binding-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-http-filesystem.spec/pom.xml b/specs/binding-http-filesystem.spec/pom.xml index 1cffacf0a7..6ae883ea03 100644 --- a/specs/binding-http-filesystem.spec/pom.xml +++ b/specs/binding-http-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-http-kafka.spec/pom.xml b/specs/binding-http-kafka.spec/pom.xml index 274a359dd2..8b32779d21 100644 --- a/specs/binding-http-kafka.spec/pom.xml +++ b/specs/binding-http-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-http.spec/pom.xml b/specs/binding-http.spec/pom.xml index 5c73e8a3cf..5c7806fb58 100644 --- a/specs/binding-http.spec/pom.xml +++ b/specs/binding-http.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl index 20887c8bdd..5bffe232f1 100644 --- a/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl +++ b/specs/binding-http.spec/src/main/resources/META-INF/zilla/http.idl @@ -49,4 +49,26 @@ scope http HttpHeader[] headers; } } + + scope event + { + enum HttpEventType (uint8) + { + REQUEST_ACCEPTED (1) + } + + struct HttpRequestAccepted extends core::event::Event + { + string8 identity; + string8 scheme; + string8 method; + string16 authority; + string16 path; + } + + union HttpEvent switch (HttpEventType) + { + case REQUEST_ACCEPTED: HttpRequestAccepted requestAccepted; + } + } } diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json index 2ae7449415..8c45102023 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/schema/http.schema.patch.json @@ -147,88 +147,7 @@ } ] }, - "authorization": - { - "title": "Authorizations", - "type": "object", - "patternProperties": - { - "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": - { - "title": "Authorization", - "type": "object", - "properties": - { - "credentials": - { - "title": "Credentials", - "type": "object", - "properties": - { - "cookies": - { - "title": "Cookies", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "headers": - { - "title": "Headers", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - }, - "query": - { - "title": "Query Parameters", - "type": "object", - "additionalProperties": - { - "type": "string", - "pattern": ".*\\{credentials\\}.*" - } - } - }, - "additionalProperties": false, - "anyOf": - [ - { - "required": - [ - "cookies" - ] - }, - { - "required": - [ - "headers" - ] - }, - { - "required": - [ - "query" - ] - } - ] - } - }, - "additionalProperties": false, - "required": - [ - "credentials" - ] - } - }, - "maxProperties": 1 - }, + "authorization": "$defs/options/binding/http/authorization", "overrides": { "title": "Overrides", @@ -469,5 +388,94 @@ ] } } + }, + { + "op": "add", + "path": "/$defs/options/binding/http", + "value": + { + "authorization": + { + "title": "Authorizations", + "type": "object", + "patternProperties": + { + "^[a-zA-Z]+[a-zA-Z0-9\\._\\-]*$": + { + "title": "Authorization", + "type": "object", + "properties": + { + "credentials": + { + "title": "Credentials", + "type": "object", + "properties": + { + "cookies": + { + "title": "Cookies", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + }, + "headers": + { + "title": "Headers", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + }, + "query": + { + "title": "Query Parameters", + "type": "object", + "additionalProperties": + { + "type": "string", + "pattern": ".*\\{credentials\\}.*" + } + } + }, + "additionalProperties": false, + "anyOf": + [ + { + "required": + [ + "cookies" + ] + }, + { + "required": + [ + "headers" + ] + }, + { + "required": + [ + "query" + ] + } + ] + } + }, + "additionalProperties": false, + "required": + [ + "credentials" + ] + } + }, + "maxProperties": 1 + } + } } ] diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt index 90377a5931..2369372d0d 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/client.rpt @@ -33,7 +33,7 @@ connected write "GET / HTTP/1.1" "\r\n" "Host: example.com:9090" "\r\n" - "Authorization: Bearer EXPIRED" "\r\n" + "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n" "\r\n" write flush diff --git a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt index 5761cef887..f091a35b5d 100644 --- a/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt +++ b/specs/binding-http.spec/src/main/scripts/io/aklivity/zilla/specs/binding/http/streams/network/rfc7230/authorization/reject.credentials.header/server.rpt @@ -32,7 +32,7 @@ connected read "GET / HTTP/1.1" "\r\n" "Host: example.com:9090" "\r\n" - "Authorization: Bearer EXPIRED" "\r\n" + "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImV4YW1wbGUifQ.eyJhdWQiOiJodHRwczovL2FwaS5leGFtcGxlLmNvbSIsImV4cCI6MTcwODk0MDQ0MCwiaXNzIjoiaHR0cHM6Ly9hdXRoLmV4YW1wbGUuY29tIiwic3ViIjoidXNlciJ9.KwZt_rDTDEOQ33URwZ8Gd1WqQjAIJESbt5Et309Yz7AJm1wWWyFhWm7AV6lt1rkX-eD-jVh0wHAsy7L4kdzBOErCdwFcdaB4u2jFDGn_9IK28lCedxIYmYtI4qn6eY916IIqRpwZcqzw08OEljfYUKo4UeX7JPAtha0GQmfZY1-NNcncg06xw3xkKSZ1SnIh9MZM1FNH_5QPZPL4NHP7DRXtaMn2w6YpO7n695Sc_3LuSDlfDDMVIUWuEreOzOXem6jheGIbJ-eDKhIXXPrOz1NBAJmvizbugMR7m_bodiEzzqt5ttKDs1974alrR_sYP8OYmR_rCqXc5N3lp_SW3A" "\r\n" "\r\n" read closed diff --git a/specs/binding-kafka-grpc.spec/pom.xml b/specs/binding-kafka-grpc.spec/pom.xml index 4947b09897..4428a0d9cf 100644 --- a/specs/binding-kafka-grpc.spec/pom.xml +++ b/specs/binding-kafka-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-kafka.spec/pom.xml b/specs/binding-kafka.spec/pom.xml index 7184b9df8a..508d080b0f 100644 --- a/specs/binding-kafka.spec/pom.xml +++ b/specs/binding-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl index 62873faacb..5c66e319b4 100644 --- a/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl +++ b/specs/binding-kafka.spec/src/main/resources/META-INF/zilla/kafka.idl @@ -533,4 +533,25 @@ scope kafka int32 index; } } + + scope event + { + enum KafkaEventType (uint8) + { + AUTHORIZATION_FAILED (1), + API_VERSION_REJECTED (2) + } + + struct KafkaApiVersionRejected extends core::event::Event + { + int32 apiKey; + int32 apiVersion; + } + + union KafkaEvent switch (KafkaEventType) + { + case AUTHORIZATION_FAILED: core::event::Event authorizationFailed; + case API_VERSION_REJECTED: KafkaApiVersionRejected apiVersionRejected; + } + } } diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml new file mode 100644 index 0000000000..c0d01b7cb4 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/config/cache.options.parameterized.topic.validate.yaml @@ -0,0 +1,65 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +--- +name: test +catalogs: + test0: + type: test + options: + id: 1 + schema: | + { + "fields": [ + { + "name": "id", + "type": "string" + }, + { + "name": "status", + "type": "string" + } + ], + "name": "Event", + "namespace": "io.aklivity.example", + "type": "record" + } +bindings: + app0: + type: kafka + kind: cache_client + routes: + - exit: cache0 + when: + - topic: test.* + cache0: + type: kafka + kind: cache_server + options: + topics: + - name: test.{id} + value: + model: test + capability: read + length: 13 + catalog: + test0: + - id: 1 + routes: + - exit: app1 + when: + - topic: test.* + diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json index 9e561112d5..ab50bee880 100644 --- a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/schema/kafka.schema.patch.json @@ -101,123 +101,7 @@ "pattern": "([^\\:]+):(\\d+)" } }, - "sasl": - { - "title": "SASL", - "type": "object", - "properties": - { - "mechanism": - { - "title": "Mechanism", - "type": "string", - "enum": [ "plain", "scram-sha-1", "scram-sha-256", "scram-sha-512" ] - } - }, - "oneOf": - [ - { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "plain" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] - }, - { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "scram-sha-1" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] - }, - { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "scram-sha-256" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] - }, - { - "additionalProperties": false, - "properties": - { - "mechanism": - { - "const": "scram-sha-512" - }, - "username": - { - "title": "Username", - "type": "string" - }, - "password": - { - "title": "Password", - "type": "string" - } - }, - "required": - [ - "username", - "password" - ] - } - ] - } + "sasl": "$defs/options/binding/kafka/sasl" }, "additionalProperties": false }, @@ -270,5 +154,129 @@ ] } } + }, + { + "op": "add", + "path": "/$defs/options/binding/kafka", + "value": + { + "sasl": + { + "title": "SASL", + "type": "object", + "properties": + { + "mechanism": + { + "title": "Mechanism", + "type": "string", + "enum": [ "plain", "scram-sha-1", "scram-sha-256", "scram-sha-512" ] + } + }, + "oneOf": + [ + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "plain" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + }, + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "scram-sha-1" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + }, + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "scram-sha-256" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + }, + { + "additionalProperties": false, + "properties": + { + "mechanism": + { + "const": "scram-sha-512" + }, + "username": + { + "title": "Username", + "type": "string" + }, + "password": + { + "title": "Password", + "type": "string" + } + }, + "required": + [ + "username", + "password" + ] + } + ] + } + } } ] diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt new file mode 100644 index 0000000000..721f8e34cc --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/client.rpt @@ -0,0 +1,32 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 16 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + + diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt new file mode 100644 index 0000000000..939c5ae746 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.invalid/server.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" +write flush diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt new file mode 100644 index 0000000000..27f271c048 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/client.rpt @@ -0,0 +1,39 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app0" + option zilla:window 16 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .merged() + .fetch() + .partition(0, 1, 2) + .build() + .build()} +read ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt new file mode 100644 index 0000000000..939c5ae746 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/merged.fetch.from.parameterized.topic.value.valid/server.rpt @@ -0,0 +1,46 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app0" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .merged() + .capabilities("FETCH_ONLY") + .topic("test.id0") + .partition(0, 1) + .build() + .build()} + +connected + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .merged() + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" +write flush diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt new file mode 100644 index 0000000000..64830fe84f --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/client.rpt @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} + +read notify RECEIVED_CONFIG + +connect await RECEIVED_CONFIG + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +read notify PARTITION_COUNT_2 + +connect await PARTITION_COUNT_2 + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .fetch() + .partition(0, 1, 2) + .build() + .build()} +read ${kafka:varint(3)} "id0" diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt new file mode 100644 index 0000000000..4ed2c70790 --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.invalid/server.rpt @@ -0,0 +1,139 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" +write flush diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt new file mode 100644 index 0000000000..cfde43db7d --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/client.rpt @@ -0,0 +1,136 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +connect "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} + +read notify RECEIVED_CONFIG + +connect await RECEIVED_CONFIG + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +read zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +read notify PARTITION_COUNT_2 + +connect await PARTITION_COUNT_2 + "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} + +read zilla:data.ext ${kafka:matchDataEx() + .typeId(zilla:id("kafka")) + .fetch() + .partition(0, 1, 2) + .build() + .build()} +read ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" diff --git a/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt new file mode 100644 index 0000000000..f05f35fced --- /dev/null +++ b/specs/binding-kafka.spec/src/main/scripts/io/aklivity/zilla/specs/binding/kafka/streams/application/merged/unmerged.fetch.from.parameterized.topic.value.valid/server.rpt @@ -0,0 +1,139 @@ +# +# Copyright 2021-2023 Aklivity Inc. +# +# Aklivity licenses this file to you 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: +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +property deltaMillis 0L +property newTimestamp ${kafka:timestamp() + deltaMillis} + +accept "zilla://streams/app1" + option zilla:window 8192 + option zilla:transmission "half-duplex" + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .describe() + .topic("test.id0") + .config("cleanup.policy") + .config("max.message.bytes") + .config("segment.bytes") + .config("segment.index.bytes") + .config("segment.ms") + .config("retention.bytes") + .config("retention.ms") + .config("delete.retention.ms") + .config("min.compaction.lag.ms") + .config("max.compaction.lag.ms") + .config("min.cleanable.dirty.ratio") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .describe() + .config("cleanup.policy", "delete") + .config("max.message.bytes", 1000012) + .config("segment.bytes", 1073741824) + .config("segment.index.bytes", 10485760) + .config("segment.ms", 604800000) + .config("retention.bytes", -1) + .config("retention.ms", 604800000) + .config("delete.retention.ms", 86400000) + .config("min.compaction.lag.ms", 0) + .config("max.compaction.lag.ms", 9223372036854775807) + .config("min.cleanable.dirty.ratio", 0.5) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .meta() + .topic("test.id0") + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .meta() + .partition(0, 1) + .build() + .build()} +write flush + +accepted + +read zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, -2) + .build() + .build()} + +connected + +write zilla:begin.ext ${kafka:beginEx() + .typeId(zilla:id("kafka")) + .fetch() + .topic("test.id0") + .partition(0, 1, 2) + .build() + .build()} +write flush + +write zilla:data.ext ${kafka:dataEx() + .typeId(zilla:id("kafka")) + .fetch() + .timestamp(newTimestamp) + .partition(0, 1, 2) + .build() + .build()} +write ${kafka:varint(3)} "id0" ${kafka:varint(8)} "positive" +write flush diff --git a/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java b/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java index fccc447235..3cfd92161f 100644 --- a/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java +++ b/specs/binding-kafka.spec/src/test/java/io/aklivity/zilla/specs/binding/kafka/streams/application/MergedIT.java @@ -153,6 +153,24 @@ public void shouldFetchMergedMessageValueValid() throws Exception k3po.finish(); } + @Test + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.valid/client", + "${app}/merged.fetch.from.parameterized.topic.value.valid/server"}) + public void shouldFetchMergedFromParameterizedTopicValid() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/merged.fetch.from.parameterized.topic.value.invalid/client", + "${app}/merged.fetch.from.parameterized.topic.value.invalid/server"}) + public void shouldFetchMergedFromParameterizedTopicInvalid() throws Exception + { + k3po.finish(); + } + @Test @Specification({ "${app}/merged.fetch.message.value.invalid/client", @@ -429,6 +447,24 @@ public void shouldFetchUnmergedMessageValueValid() throws Exception k3po.finish(); } + @Test + @Specification({ + "${app}/unmerged.fetch.from.parameterized.topic.value.valid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.valid/server"}) + public void shouldFetchUnmergedFromParameterizedTopicValid() throws Exception + { + k3po.finish(); + } + + @Test + @Specification({ + "${app}/unmerged.fetch.from.parameterized.topic.value.invalid/client", + "${app}/unmerged.fetch.from.parameterized.topic.value.invalid/server"}) + public void shouldFetchUnmergedFromParameterizedTopicInvalid() throws Exception + { + k3po.finish(); + } + @Test @Specification({ "${app}/unmerged.fetch.message.values/client", diff --git a/specs/binding-mqtt-kafka.spec/pom.xml b/specs/binding-mqtt-kafka.spec/pom.xml index 7b8b0dac1c..bd7148400b 100644 --- a/specs/binding-mqtt-kafka.spec/pom.xml +++ b/specs/binding-mqtt-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-mqtt.spec/pom.xml b/specs/binding-mqtt.spec/pom.xml index 665617f536..207aa18298 100644 --- a/specs/binding-mqtt.spec/pom.xml +++ b/specs/binding-mqtt.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl index 26730b7747..9431a6b159 100644 --- a/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl +++ b/specs/binding-mqtt.spec/src/main/resources/META-INF/zilla/mqtt.idl @@ -260,6 +260,5 @@ scope mqtt COMPLETE(0), INCOMPLETE(1) } - } } diff --git a/specs/binding-proxy.spec/pom.xml b/specs/binding-proxy.spec/pom.xml index b7d00de77c..ebbe76284f 100644 --- a/specs/binding-proxy.spec/pom.xml +++ b/specs/binding-proxy.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-sse-kafka.spec/pom.xml b/specs/binding-sse-kafka.spec/pom.xml index 14af1095ef..0f862462a7 100644 --- a/specs/binding-sse-kafka.spec/pom.xml +++ b/specs/binding-sse-kafka.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-sse.spec/pom.xml b/specs/binding-sse.spec/pom.xml index ed8a8baebd..4b1c4226a6 100644 --- a/specs/binding-sse.spec/pom.xml +++ b/specs/binding-sse.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/binding-tcp.spec/pom.xml b/specs/binding-tcp.spec/pom.xml index 7427602777..49e432089b 100644 --- a/specs/binding-tcp.spec/pom.xml +++ b/specs/binding-tcp.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml @@ -73,6 +73,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core tcp proxy + io.aklivity.zilla.specs.binding.tcp.internal.types + + + + + generate + + + + com.mycila license-maven-plugin diff --git a/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl new file mode 100644 index 0000000000..a796c117f5 --- /dev/null +++ b/specs/binding-tcp.spec/src/main/resources/META-INF/zilla/tcp.idl @@ -0,0 +1,35 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope tcp +{ + scope event + { + enum TcpEventType (uint8) + { + DNS_FAILED (1) + } + + struct TcpDnsFailed extends core::event::Event + { + string16 address; + } + + union TcpEvent switch (TcpEventType) + { + case DNS_FAILED: TcpDnsFailed dnsFailed; + } + } +} diff --git a/specs/binding-tls.spec/pom.xml b/specs/binding-tls.spec/pom.xml index a044f93779..27ec2ca9e6 100644 --- a/specs/binding-tls.spec/pom.xml +++ b/specs/binding-tls.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml @@ -78,6 +78,22 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core tls proxy + io.aklivity.zilla.specs.binding.tls.internal.types + + + + + generate + + + + com.mycila license-maven-plugin diff --git a/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl new file mode 100644 index 0000000000..c81e96e2c7 --- /dev/null +++ b/specs/binding-tls.spec/src/main/resources/META-INF/zilla/tls.idl @@ -0,0 +1,38 @@ +/* + * Copyright 2021-2023 Aklivity Inc. + * + * Aklivity licenses this file to you 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: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +scope tls +{ + scope event + { + enum TlsEventType (uint8) + { + TLS_FAILED (1), + TLS_PROTOCOL_REJECTED (2), + TLS_KEY_REJECTED (3), + TLS_PEER_NOT_VERIFIED (4), + TLS_HANDSHAKE_FAILED (5) + } + + union TlsEvent switch (TlsEventType) + { + case TLS_FAILED: core::event::Event tlsFailed; + case TLS_PROTOCOL_REJECTED: core::event::Event tlsProtocolRejected; + case TLS_KEY_REJECTED: core::event::Event tlsKeyRejected; + case TLS_PEER_NOT_VERIFIED: core::event::Event tlsPeerNotVerified; + case TLS_HANDSHAKE_FAILED: core::event::Event tlsHandshakeFailed; + } + } +} diff --git a/specs/binding-ws.spec/pom.xml b/specs/binding-ws.spec/pom.xml index c511415364..157e53b353 100644 --- a/specs/binding-ws.spec/pom.xml +++ b/specs/binding-ws.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/engine.spec/pom.xml b/specs/engine.spec/pom.xml index 86d1a59a81..3dc193021c 100644 --- a/specs/engine.spec/pom.xml +++ b/specs/engine.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl index bc78dd0888..05dfc800aa 100644 --- a/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl +++ b/specs/engine.spec/src/main/resources/META-INF/zilla/core.idl @@ -100,4 +100,14 @@ scope core octets extension; } } + + scope event + { + struct Event + { + int64 timestamp; + int64 traceId; + int64 namespacedId; + } + } } diff --git a/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json b/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json index b1ef9202ab..dd2cc277ad 100644 --- a/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json +++ b/specs/engine.spec/src/main/scripts/io/aklivity/zilla/specs/engine/schema/engine.schema.json @@ -303,6 +303,12 @@ } ] }, + "options": + { + "binding": + { + } + }, "binding": { "title": "Binding", diff --git a/specs/exporter-prometheus.spec/pom.xml b/specs/exporter-prometheus.spec/pom.xml index 89f71f869a..3dbf6f0648 100644 --- a/specs/exporter-prometheus.spec/pom.xml +++ b/specs/exporter-prometheus.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json b/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json index 4eee03c61a..336ed01046 100644 --- a/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json +++ b/specs/exporter-prometheus.spec/src/main/scripts/io/aklivity/zilla/specs/exporter/prometheus/schema/prometheus.schema.patch.json @@ -41,6 +41,7 @@ "scheme": { "title": "Scheme", + "type": "string", "enum": [ "http" diff --git a/specs/guard-jwt.spec/pom.xml b/specs/guard-jwt.spec/pom.xml index 4a4a088b0e..d643c45e99 100644 --- a/specs/guard-jwt.spec/pom.xml +++ b/specs/guard-jwt.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml @@ -63,6 +63,23 @@ org.jasig.maven maven-notice-plugin + + ${project.groupId} + flyweight-maven-plugin + ${project.version} + + core jwt + io.aklivity.zilla.specs.guard.jwt.internal.types + + + + + validate + generate + + + + com.mycila license-maven-plugin @@ -86,6 +103,9 @@ org.jacoco jacoco-maven-plugin + + io/aklivity/zilla/specs/guard/jwt/internal/types/**/*.class + BUNDLE diff --git a/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl new file mode 100644 index 0000000000..997f9bf859 --- /dev/null +++ b/specs/guard-jwt.spec/src/main/resources/META-INF/zilla/jwt.idl @@ -0,0 +1,34 @@ +/* + * Copyright 2021-2023 Aklivity Inc + * + * Licensed under the Aklivity Community License (the "License"); you may not use + * this file except in compliance with the License. You may obtain a copy of the + * License at + * + * https://www.aklivity.io/aklivity-community-license/ + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + */ +scope jwt +{ + scope event + { + enum JwtEventType (uint8) + { + AUTHORIZATION_FAILED (1) + } + + struct JwtAuthorizationFailed extends core::event::Event + { + string8 identity; + } + + union JwtEvent switch (JwtEventType) + { + case AUTHORIZATION_FAILED: JwtAuthorizationFailed authorizationFailed; + } + } +} diff --git a/specs/metrics-grpc.spec/pom.xml b/specs/metrics-grpc.spec/pom.xml index 5dbeac0ed6..545584affb 100644 --- a/specs/metrics-grpc.spec/pom.xml +++ b/specs/metrics-grpc.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/metrics-http.spec/pom.xml b/specs/metrics-http.spec/pom.xml index e972c15d6e..5889746ced 100644 --- a/specs/metrics-http.spec/pom.xml +++ b/specs/metrics-http.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/metrics-stream.spec/pom.xml b/specs/metrics-stream.spec/pom.xml index 40039e52c6..684031789a 100644 --- a/specs/metrics-stream.spec/pom.xml +++ b/specs/metrics-stream.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/pom.xml b/specs/pom.xml index f1d1c61481..c5117a86ee 100644 --- a/specs/pom.xml +++ b/specs/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla zilla - 0.9.68 + 0.9.69 ../pom.xml diff --git a/specs/vault-filesystem.spec/pom.xml b/specs/vault-filesystem.spec/pom.xml index 9a480d7115..a1ad40231c 100644 --- a/specs/vault-filesystem.spec/pom.xml +++ b/specs/vault-filesystem.spec/pom.xml @@ -8,7 +8,7 @@ io.aklivity.zilla specs - 0.9.68 + 0.9.69 ../pom.xml