diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 81c7b58d88..8f3c521907 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -7,6 +7,11 @@ assignees: ''
---
+
+
**Describe the bug**
A clear and concise description of what the bug is.
@@ -23,16 +28,46 @@ A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
-**Desktop (please complete the following information):**
- - OS: [e.g. iOS]
- - Browser [e.g. chrome, safari]
- - Version [e.g. 22]
+**Zilla Environment:**
+
+
+**Attach the `zilla.yaml` config file:**
+
+**Attach the `zilla dump` pcap file:**
+
+```
+ZILLA_INCUBATOR_ENABLED=true ./zilla dump --verbose --write /tmp/zilla.pcap
+```
+
+**Kafka Environment:**
+
+- Provider: [e.g. Kafka, Confluent, Redpanda]
+- Version: [e.g. 22]
+- Config: [e.g. log compaction, Sasl]
-**Smartphone (please complete the following information):**
- - Device: [e.g. iPhone6]
- - OS: [e.g. iOS8.1]
- - Browser [e.g. stock browser, safari]
- - Version [e.g. 22]
+**Client Environment:**
+
+ - Service: [e.g. IoT, Microservice, REST client]
+ - Library/SDK:
+ - Browser:
+ - Version:
**Additional context**
Add any other context about the problem here.
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 0e2f3c21e8..e105e8ff2f 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -12,11 +12,7 @@ updates:
schedule:
interval: daily
- package-ecosystem: docker
- directory: /cloud/docker-image/src/main/docker/release
- schedule:
- interval: daily
-- package-ecosystem: docker
- directory: /cloud/docker-image/src/main/docker/incubator
+ directory: /cloud/docker-image/src/main/docker
schedule:
interval: daily
- package-ecosystem: github-actions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e74b31566f..a8024d1a35 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [ 11, 17, 20 ]
+ java: [ 17, 21, 22 ]
steps:
- name: Checkout GitHub sources
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 93393ccb4b..1848bc319c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,11 +2,58 @@
## [Unreleased](https://github.com/aklivity/zilla/tree/HEAD)
-[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.78...HEAD)
+[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.79...HEAD)
+
+**Breaking changes:**
+
+- Remove `zilla generate` command [\#960](https://github.com/aklivity/zilla/issues/960)
+- Report unused properties based on binding definition [\#808](https://github.com/aklivity/zilla/issues/808)
+
+**Implemented enhancements:**
+
+- Update the Zilla issue Bug Report template with debugging info collection instructions [\#991](https://github.com/aklivity/zilla/issues/991)
+- Support multiple specs in `openapi-asyncapi` binding [\#964](https://github.com/aklivity/zilla/issues/964)
+- Integrate JMH into `tls` binding [\#961](https://github.com/aklivity/zilla/issues/961)
+- Enhance validation for `openapi` and `asyncapi` bindings [\#950](https://github.com/aklivity/zilla/issues/950)
+- Support multiple specs in `openapi` binding [\#949](https://github.com/aklivity/zilla/issues/949)
+- Support multiple specs in `asyncapi` binding [\#948](https://github.com/aklivity/zilla/issues/948)
+- Support `asyncapi` `mqtt` streetlights mapping to `kafka` streetlights [\#947](https://github.com/aklivity/zilla/issues/947)
+- Support `mqtt` access log [\#945](https://github.com/aklivity/zilla/issues/945)
+- Support `mqtt` client binding authorization [\#940](https://github.com/aklivity/zilla/issues/940)
+- Resiliently handle `apicurio` catalog unreachable [\#938](https://github.com/aklivity/zilla/issues/938)
+- Resiliently handle `karapace` catalog unreachable [\#937](https://github.com/aklivity/zilla/issues/937)
+- Support local `zilla` installation on MacOS via `homebrew` [\#680](https://github.com/aklivity/zilla/issues/680)
+
+**Fixed bugs:**
+
+- Zilla crashes with `IllegalArgumentException` when an Avro payload is fetched as `json` [\#1025](https://github.com/aklivity/zilla/issues/1025)
+- MQTT-Kafka qos2: increasing tracked producer sequence number without publishing to Kafka [\#1014](https://github.com/aklivity/zilla/issues/1014)
+- OTLP `logs` signal doesn't show up in OpenTelemetry Demo [\#1006](https://github.com/aklivity/zilla/issues/1006)
+- Flow control issue in openapi binding [\#1004](https://github.com/aklivity/zilla/issues/1004)
+- `mqtt` connecting with longer client id fails [\#1003](https://github.com/aklivity/zilla/issues/1003)
+- Running zilla with the `kafka-grpc` binding in a cluster with multiple instances results in each instance delivering a message to the configured `remote_server` [\#882](https://github.com/aklivity/zilla/issues/882)
+- Using the `grpc.kafka.proxy` example setup, Zilla will periodically not deliver the message to the gRPC service defined in the `kafka-grpc` binding [\#881](https://github.com/aklivity/zilla/issues/881)
+- Flaky kafka-grpc test [\#768](https://github.com/aklivity/zilla/issues/768)
+
+## [0.9.79](https://github.com/aklivity/zilla/tree/0.9.79) (2024-04-22)
+
+[Full Changelog](https://github.com/aklivity/zilla/compare/0.9.78...0.9.79)
**Implemented enhancements:**
- Support `filesystem` catalog for local schemas [\#908](https://github.com/aklivity/zilla/issues/908)
+- Check for files on startup when the zilla.yaml specifies paths to files or directories [\#292](https://github.com/aklivity/zilla/issues/292)
+
+**Fixed bugs:**
+
+- Fix k3po does not reload labels when labels file size decreases [\#972](https://github.com/aklivity/zilla/pull/972) ([bmaidics](https://github.com/bmaidics))
+
+**Merged pull requests:**
+
+- Support config for mqtt publish qos max [\#971](https://github.com/aklivity/zilla/pull/971) ([jfallows](https://github.com/jfallows))
+- Use default kafka client id for kafka client instance id [\#968](https://github.com/aklivity/zilla/pull/968) ([jfallows](https://github.com/jfallows))
+- Add vault parameter to exporter [\#966](https://github.com/aklivity/zilla/pull/966) ([attilakreiner](https://github.com/attilakreiner))
+- Implement filesystem catalog [\#962](https://github.com/aklivity/zilla/pull/962) ([bmaidics](https://github.com/bmaidics))
## [0.9.78](https://github.com/aklivity/zilla/tree/0.9.78) (2024-04-16)
diff --git a/build/flyweight-maven-plugin/pom.xml b/build/flyweight-maven-plugin/pom.xml
index 835a1082cd..12dd0ae5c3 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.79
+ 0.9.80
../pom.xml
diff --git a/build/pom.xml b/build/pom.xml
index 86a6313d31..0589f7e41a 100644
--- a/build/pom.xml
+++ b/build/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
zilla
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/cloud/docker-image/pom.xml b/cloud/docker-image/pom.xml
index fb65026b3e..fdd6dd1ba0 100644
--- a/cloud/docker-image/pom.xml
+++ b/cloud/docker-image/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
cloud
- 0.9.79
+ 0.9.80
../pom.xml
@@ -199,12 +199,6 @@
${project.version}
runtime
-
- ${project.groupId}
- command-generate
- ${project.version}
- runtime
-
${project.groupId}
command-metrics
diff --git a/cloud/docker-image/src/main/docker/Dockerfile b/cloud/docker-image/src/main/docker/Dockerfile
index 876a1c77af..6717305625 100644
--- a/cloud/docker-image/src/main/docker/Dockerfile
+++ b/cloud/docker-image/src/main/docker/Dockerfile
@@ -25,7 +25,7 @@ RUN cat zpm.json.template | sed "s/\${VERSION}/${project.version}/g" | tee zpm.j
RUN ./zpmw install --debug --exclude-remote-repositories
RUN ./zpmw clean --keep-image
-FROM ubuntu:jammy-20240111
+FROM ubuntu:jammy-20240427
ENV ZILLA_VERSION ${project.version}
diff --git a/cloud/docker-image/src/main/docker/alpine.Dockerfile b/cloud/docker-image/src/main/docker/alpine.Dockerfile
index 3103f97070..4dc74d18e9 100644
--- a/cloud/docker-image/src/main/docker/alpine.Dockerfile
+++ b/cloud/docker-image/src/main/docker/alpine.Dockerfile
@@ -27,7 +27,7 @@ RUN apk add --no-cache wget
RUN ./zpmw install --debug --exclude-remote-repositories
RUN ./zpmw clean --keep-image
-FROM alpine:3.19.0
+FROM alpine:3.19.1
ENV ZILLA_VERSION ${project.version}
diff --git a/cloud/docker-image/src/main/docker/zpm.json.template b/cloud/docker-image/src/main/docker/zpm.json.template
index 392e048a10..46961f11bb 100644
--- a/cloud/docker-image/src/main/docker/zpm.json.template
+++ b/cloud/docker-image/src/main/docker/zpm.json.template
@@ -42,7 +42,6 @@
"io.aklivity.zilla:common",
"io.aklivity.zilla:command",
"io.aklivity.zilla:command-dump",
- "io.aklivity.zilla:command-generate",
"io.aklivity.zilla:command-metrics",
"io.aklivity.zilla:command-start",
"io.aklivity.zilla:command-stop",
diff --git a/cloud/helm-chart/pom.xml b/cloud/helm-chart/pom.xml
index 8867fc87fb..c01ff5d7f6 100644
--- a/cloud/helm-chart/pom.xml
+++ b/cloud/helm-chart/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
cloud
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/cloud/pom.xml b/cloud/pom.xml
index 2aeb72ff2e..d6c406172e 100644
--- a/cloud/pom.xml
+++ b/cloud/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
zilla
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/conf/pom.xml b/conf/pom.xml
index d0baf21a82..8fdd8361b7 100644
--- a/conf/pom.xml
+++ b/conf/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
zilla
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/incubator/binding-amqp.spec/pom.xml b/incubator/binding-amqp.spec/pom.xml
index 331009404b..c48b18baed 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.79
+ 0.9.80
../pom.xml
diff --git a/incubator/binding-amqp/pom.xml b/incubator/binding-amqp/pom.xml
index d76570cb0f..61297a313b 100644
--- a/incubator/binding-amqp/pom.xml
+++ b/incubator/binding-amqp/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
incubator
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/incubator/catalog-filesystem.spec/pom.xml b/incubator/catalog-filesystem.spec/pom.xml
index 499001b2b9..68902b8bac 100644
--- a/incubator/catalog-filesystem.spec/pom.xml
+++ b/incubator/catalog-filesystem.spec/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
incubator
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/incubator/catalog-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/filesystem/config/event.yaml b/incubator/catalog-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/filesystem/config/event.yaml
index 63872968f3..7200e67713 100644
--- a/incubator/catalog-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/filesystem/config/event.yaml
+++ b/incubator/catalog-filesystem.spec/src/main/scripts/io/aklivity/zilla/specs/catalog/filesystem/config/event.yaml
@@ -36,6 +36,7 @@ bindings:
type: test
kind: server
options:
- catalogs:
- - catalog0
+ catalog:
+ catalog0:
+ - subject: not-subject1
exit: app0
diff --git a/incubator/catalog-filesystem/pom.xml b/incubator/catalog-filesystem/pom.xml
index 47e8191d91..9813714ff1 100644
--- a/incubator/catalog-filesystem/pom.xml
+++ b/incubator/catalog-filesystem/pom.xml
@@ -6,7 +6,7 @@
io.aklivity.zilla
incubator
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/incubator/catalog-filesystem/src/test/java/io/aklivity/zilla/runtime/catalog/filesystem/internal/EventIT.java b/incubator/catalog-filesystem/src/test/java/io/aklivity/zilla/runtime/catalog/filesystem/internal/EventIT.java
index 21180d34df..e7b956eb83 100644
--- a/incubator/catalog-filesystem/src/test/java/io/aklivity/zilla/runtime/catalog/filesystem/internal/EventIT.java
+++ b/incubator/catalog-filesystem/src/test/java/io/aklivity/zilla/runtime/catalog/filesystem/internal/EventIT.java
@@ -49,8 +49,8 @@ public class EventIT
@Test
@Configuration("event.yaml")
@Specification({
- "${net}/event/client",
- "${app}/event/server"
+ "${net}/handshake/client",
+ "${app}/handshake/server"
})
public void shouldLogEvents() throws Exception
{
diff --git a/incubator/command-dump/pom.xml b/incubator/command-dump/pom.xml
index f2fb397d84..7d2e7cc481 100644
--- a/incubator/command-dump/pom.xml
+++ b/incubator/command-dump/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
incubator
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/incubator/command-dump/src/main/java/io/aklivity/zilla/runtime/command/dump/internal/airline/spy/OneToOneRingBufferSpy.java b/incubator/command-dump/src/main/java/io/aklivity/zilla/runtime/command/dump/internal/airline/spy/OneToOneRingBufferSpy.java
index 81721342cd..dc5ab7394b 100644
--- a/incubator/command-dump/src/main/java/io/aklivity/zilla/runtime/command/dump/internal/airline/spy/OneToOneRingBufferSpy.java
+++ b/incubator/command-dump/src/main/java/io/aklivity/zilla/runtime/command/dump/internal/airline/spy/OneToOneRingBufferSpy.java
@@ -42,7 +42,7 @@ public OneToOneRingBufferSpy(
final AtomicBuffer buffer)
{
this.buffer = buffer;
- checkCapacity(buffer.capacity());
+ checkCapacity(buffer.capacity(), 0);
capacity = buffer.capacity() - TRAILER_LENGTH;
buffer.verifyAlignment();
diff --git a/incubator/command-generate/COPYRIGHT b/incubator/command-generate/COPYRIGHT
deleted file mode 100644
index 0cb10b6f62..0000000000
--- a/incubator/command-generate/COPYRIGHT
+++ /dev/null
@@ -1,12 +0,0 @@
-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/command-generate/LICENSE b/incubator/command-generate/LICENSE
deleted file mode 100644
index f6abb6327b..0000000000
--- a/incubator/command-generate/LICENSE
+++ /dev/null
@@ -1,114 +0,0 @@
- 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/command-generate/NOTICE b/incubator/command-generate/NOTICE
deleted file mode 100644
index 17478992e3..0000000000
--- a/incubator/command-generate/NOTICE
+++ /dev/null
@@ -1,18 +0,0 @@
-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/command-generate/NOTICE.template b/incubator/command-generate/NOTICE.template
deleted file mode 100644
index 209ca12f74..0000000000
--- a/incubator/command-generate/NOTICE.template
+++ /dev/null
@@ -1,13 +0,0 @@
-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/command-generate/mvnw b/incubator/command-generate/mvnw
deleted file mode 100755
index d2f0ea3808..0000000000
--- a/incubator/command-generate/mvnw
+++ /dev/null
@@ -1,310 +0,0 @@
-#!/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/command-generate/mvnw.cmd b/incubator/command-generate/mvnw.cmd
deleted file mode 100644
index b26ab24f03..0000000000
--- a/incubator/command-generate/mvnw.cmd
+++ /dev/null
@@ -1,182 +0,0 @@
-@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/command-generate/pom.xml b/incubator/command-generate/pom.xml
deleted file mode 100644
index 5d21938023..0000000000
--- a/incubator/command-generate/pom.xml
+++ /dev/null
@@ -1,213 +0,0 @@
-
-
-
- 4.0.0
-
- io.aklivity.zilla
- incubator
- 0.9.79
- ../pom.xml
-
-
- command-generate
- zilla::incubator::command-generate
-
-
-
- Aklivity Community License Agreement
- https://www.aklivity.io/aklivity-community-license/
- repo
-
-
-
-
- 11
- 11
- 0.60
- 4
-
-
-
-
- ${project.groupId}
- engine.spec
- ${project.version}
- provided
-
-
- ${project.groupId}
- engine
- ${project.version}
- provided
-
-
- ${project.groupId}
- command
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- binding-http
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- binding-mqtt
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- binding-tcp
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- binding-tls
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- guard-jwt
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- catalog-inline
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- model-avro
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- model-core
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- model-json
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- model-protobuf
- ${project.version}
- provided
-
-
- io.aklivity.zilla
- vault-filesystem
- ${project.version}
- provided
-
-
- com.fasterxml.jackson.dataformat
- jackson-dataformat-yaml
- 2.16.1
-
-
- org.junit.jupiter
- junit-jupiter-engine
- test
-
-
-
-
-
-
- org.jasig.maven
- maven-notice-plugin
-
-
- com.mycila
- license-maven-plugin
-
-
- src/test/resources/io/aklivity/zilla/runtime/command/generate/internal/openapi/**/*
- src/test/resources/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/**/*
-
-
-
-
- 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-failsafe-plugin
-
-
- org.jacoco
- jacoco-maven-plugin
-
-
- io/aklivity/zilla/runtime/command/generate/internal/types/**/*.class
- io/aklivity/zilla/runtime/command/generate/internal/openapi/model/*.class
- io/aklivity/zilla/runtime/command/generate/internal/openapi/model2/*.class
- io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/*.class
- io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model2/*.class
-
-
-
- BUNDLE
-
-
- INSTRUCTION
- COVEREDRATIO
- ${jacoco.coverage.ratio}
-
-
- CLASS
- MISSEDCOUNT
- ${jacoco.missed.count}
-
-
-
-
-
-
-
- ${project.groupId}
- flyweight-maven-plugin
- ${project.version}
-
- core
- io.aklivity.zilla.runtime.command.generate.internal.types
-
-
-
-
- generate
-
-
-
-
-
-
-
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ConfigGenerator.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ConfigGenerator.java
deleted file mode 100644
index 836a0d113e..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ConfigGenerator.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * 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.command.generate.internal.airline;
-
-import static org.agrona.LangUtil.rethrowUnchecked;
-
-import java.util.List;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-import jakarta.json.bind.Jsonb;
-
-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.engine.config.ModelConfig;
-import io.aklivity.zilla.runtime.model.core.config.Int32ModelConfig;
-import io.aklivity.zilla.runtime.model.core.config.StringModelConfig;
-
-public abstract class ConfigGenerator
-{
- protected static final String INLINE_CATALOG_NAME = "catalog0";
- protected static final String INLINE_CATALOG_TYPE = "inline";
- protected static final String APPLICATION_JSON = "application/json";
- protected static final String VERSION_LATEST = "latest";
- protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$");
-
- protected final Map models = Map.of(
- "string", StringModelConfig.builder().build(),
- "integer", Int32ModelConfig.builder().build()
- );
- protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher("");
-
- public abstract String generate();
-
- 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;
- }
-
- protected final String unquoteEnvVars(
- String yaml,
- List unquotedEnvVars)
- {
- for (String envVar : unquotedEnvVars)
- {
- yaml = yaml.replaceAll(
- Pattern.quote(String.format("\"${{env.%s}}\"", envVar)),
- String.format("\\${{env.%s}}", envVar)
- );
- }
- return yaml;
- }
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ZillaConfigCommand.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ZillaConfigCommand.java
deleted file mode 100644
index c0a08a7400..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ZillaConfigCommand.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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.command.generate.internal.airline;
-
-import static org.agrona.LangUtil.rethrowUnchecked;
-
-import java.io.FileInputStream;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Map;
-import java.util.function.Function;
-
-import com.github.rvesse.airline.annotations.Command;
-import com.github.rvesse.airline.annotations.Option;
-import com.github.rvesse.airline.annotations.restrictions.AllowedValues;
-import com.github.rvesse.airline.annotations.restrictions.Required;
-
-import io.aklivity.zilla.runtime.command.ZillaCommand;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.http.proxy.AsyncApiHttpProxyConfigGenerator;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.mqtt.proxy.AsyncApiMqttProxyConfigGenerator;
-import io.aklivity.zilla.runtime.command.generate.internal.openapi.http.proxy.OpenApiHttpProxyConfigGenerator;
-
-@Command(name = "generate", description = "Generate configuration file")
-public final class ZillaConfigCommand extends ZillaCommand
-{
- private static final Map> GENERATORS = Map.of(
- "openapi.http.proxy", OpenApiHttpProxyConfigGenerator::new,
- "asyncapi.http.proxy", AsyncApiHttpProxyConfigGenerator::new,
- "asyncapi.mqtt.proxy", AsyncApiMqttProxyConfigGenerator::new
- );
-
- @Option(name = {"-t", "--template"},
- description = "Template name")
- @Required
- @AllowedValues(allowedValues = {
- "openapi.http.proxy",
- "asyncapi.http.proxy",
- "asyncapi.mqtt.proxy"
- })
- public String template;
-
- @Option(name = {"-i", "--input"},
- description = "Input filename",
- typeConverterProvider = ZillaConfigCommandPathConverterProvider.class)
- public Path input;
-
- @Option(name = {"-o", "--output"},
- description = "Output filename",
- typeConverterProvider = ZillaConfigCommandPathConverterProvider.class)
- public Path output = Paths.get("zilla.yaml");
-
- @Override
- public void run()
- {
- try (InputStream inputStream = new FileInputStream(input.toFile()))
- {
- ConfigGenerator generator = GENERATORS.get(template).apply(inputStream);
- Files.writeString(output, generator.generate());
- }
- catch (Exception ex)
- {
- ex.printStackTrace();
- rethrowUnchecked(ex);
- }
- }
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ZillaConfigCommandPathConverterProvider.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ZillaConfigCommandPathConverterProvider.java
deleted file mode 100644
index 403fdc7755..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/airline/ZillaConfigCommandPathConverterProvider.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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.command.generate.internal.airline;
-
-import java.nio.file.Paths;
-
-import com.github.rvesse.airline.model.ArgumentsMetadata;
-import com.github.rvesse.airline.model.OptionMetadata;
-import com.github.rvesse.airline.parser.ParseState;
-import com.github.rvesse.airline.types.TypeConverter;
-import com.github.rvesse.airline.types.TypeConverterProvider;
-import com.github.rvesse.airline.types.numerics.NumericTypeConverter;
-
-public final class ZillaConfigCommandPathConverterProvider implements TypeConverterProvider
-{
- private final ZillaDumpCommandPathConverter converter = new ZillaDumpCommandPathConverter();
-
- private final class ZillaDumpCommandPathConverter implements TypeConverter
- {
- @Override
- public void setNumericConverter(
- NumericTypeConverter converter)
- {
- }
-
- @Override
- public Object convert(
- String name,
- Class> type,
- String value)
- {
- return Paths.get(value);
- }
- }
-
- @Override
- public TypeConverter getTypeConverter(
- OptionMetadata option,
- ParseState state)
- {
- return converter;
- }
-
- @Override
- public TypeConverter getTypeConverter(
- ArgumentsMetadata arguments,
- ParseState state)
- {
- return converter;
- }
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/AsyncApiConfigGenerator.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/AsyncApiConfigGenerator.java
deleted file mode 100644
index 243d29dcaa..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/AsyncApiConfigGenerator.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi;
-
-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 jakarta.json.bind.Jsonb;
-import jakarta.json.bind.JsonbBuilder;
-
-import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
-
-import io.aklivity.zilla.runtime.catalog.inline.config.InlineOptionsConfig;
-import io.aklivity.zilla.runtime.catalog.inline.config.InlineSchemaConfigBuilder;
-import io.aklivity.zilla.runtime.command.generate.internal.airline.ConfigGenerator;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.AsyncApi;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Message;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Schema;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.view.MessageView;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.view.SchemaView;
-import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder;
-
-public abstract class AsyncApiConfigGenerator extends ConfigGenerator
-{
- protected AsyncApi asyncApi;
-
- protected boolean hasJsonContentType()
- {
- String contentType = null;
- if (asyncApi.components != null && asyncApi.components.messages != null && !asyncApi.components.messages.isEmpty())
- {
- Message firstMessage = asyncApi.components.messages.entrySet().stream().findFirst().get().getValue();
- contentType = MessageView.of(asyncApi.components.messages, firstMessage).contentType();
- }
- return contentType != null && jsonContentType.reset(contentType).matches();
- }
-
- protected NamespaceConfigBuilder injectCatalog(
- NamespaceConfigBuilder namespace)
- {
- 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())
- {
- SchemaView schema = SchemaView.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;
- }
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/http/proxy/AsyncApiHttpProxyConfigGenerator.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/http/proxy/AsyncApiHttpProxyConfigGenerator.java
deleted file mode 100644
index cba4f9c148..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/http/proxy/AsyncApiHttpProxyConfigGenerator.java
+++ /dev/null
@@ -1,604 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.http.proxy;
-
-import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN;
-import static io.aklivity.zilla.runtime.engine.config.KindConfig.CLIENT;
-import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER;
-import static java.util.Objects.requireNonNull;
-import static org.agrona.LangUtil.rethrowUnchecked;
-
-import java.io.InputStream;
-import java.net.URI;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import jakarta.json.Json;
-import jakarta.json.JsonPatch;
-import jakarta.json.JsonPatchBuilder;
-import jakarta.json.bind.Jsonb;
-import jakarta.json.bind.JsonbBuilder;
-
-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.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.command.generate.internal.asyncapi.AsyncApiConfigGenerator;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.AsyncApi;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Item;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Message;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Operation;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Parameter;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.model.Server;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.view.ChannelView;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.view.MessageView;
-import io.aklivity.zilla.runtime.command.generate.internal.asyncapi.view.ServerView;
-import io.aklivity.zilla.runtime.engine.config.BindingConfigBuilder;
-import io.aklivity.zilla.runtime.engine.config.CatalogedConfigBuilder;
-import io.aklivity.zilla.runtime.engine.config.EngineConfig;
-import io.aklivity.zilla.runtime.engine.config.EngineConfigWriter;
-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.guard.jwt.config.JwtOptionsConfig;
-import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig;
-import io.aklivity.zilla.runtime.vault.filesystem.config.FileSystemOptionsConfig;
-
-public class AsyncApiHttpProxyConfigGenerator extends AsyncApiConfigGenerator
-{
- private final InputStream input;
-
- private int[] allPorts;
- private int[] httpPorts;
- private int[] httpsPorts;
- private boolean isPlainEnabled;
- private boolean isTlsEnabled;
- private Map securitySchemes;
- private String authorizationHeader;
- private boolean isJwtEnabled;
-
- public AsyncApiHttpProxyConfigGenerator(
- InputStream input)
- {
- this.input = input;
- }
-
- @Override
- public String generate()
- {
- this.asyncApi = parseAsyncApi(input);
- this.allPorts = resolveAllPorts();
- this.httpPorts = resolvePortsForScheme("http");
- this.httpsPorts = resolvePortsForScheme("https");
- this.isPlainEnabled = httpPorts != null;
- this.isTlsEnabled = httpsPorts != null;
- this.securitySchemes = resolveSecuritySchemes();
- this.authorizationHeader = resolveAuthorizationHeader();
- this.isJwtEnabled = !securitySchemes.isEmpty();
- EngineConfigWriter configWriter = new EngineConfigWriter(null);
- String yaml = configWriter.write(createConfig(), createEnvVarsPatch());
- return unquoteEnvVars(yaml, unquotedEnvVars());
- }
-
- private AsyncApi parseAsyncApi(
- InputStream inputStream)
- {
- AsyncApi asyncApi = null;
- try (Jsonb jsonb = JsonbBuilder.create())
- {
- asyncApi = jsonb.fromJson(inputStream, AsyncApi.class);
- }
- catch (Exception ex)
- {
- rethrowUnchecked(ex);
- }
- return asyncApi;
- }
-
- private 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++)
- {
- ServerView server = ServerView.of(asyncApi.servers.get(keys[i]));
- URI url = server.url();
- ports[i] = url.getPort();
- }
- return ports;
- }
-
- private int[] resolvePortsForScheme(
- String scheme)
- {
- requireNonNull(scheme);
- int[] ports = null;
- URI url = findFirstServerUrlWithScheme(scheme);
- if (url != null)
- {
- ports = new int[] {url.getPort()};
- }
- return ports;
- }
-
- private URI findFirstServerUrlWithScheme(
- String scheme)
- {
- requireNonNull(scheme);
- URI result = null;
- for (String key : asyncApi.servers.keySet())
- {
- ServerView server = ServerView.of(asyncApi.servers.get(key));
- if (scheme.equals(server.url().getScheme()))
- {
- result = server.url();
- break;
- }
- }
- return result;
- }
-
- 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())
- {
- Message message = entry.getValue();
- if (message.headers != null && message.headers.properties != null)
- {
- Item authorization = message.headers.properties.get("authorization");
- if (authorization != null)
- {
- result = authorization.description;
- break;
- }
- }
- }
- }
- return result;
- }
-
- private EngineConfig createConfig()
- {
- return EngineConfig.builder()
- .namespace()
- .name("example")
- .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(this::injectTlsServer)
- .binding()
- .name("http_server0")
- .type("http")
- .kind(SERVER)
- .options(HttpOptionsConfig::builder)
- .access()
- .policy(CROSS_ORIGIN)
- .build()
- .inject(this::injectHttpServerOptions)
- .inject(this::injectHttpServerRequests)
- .build()
- .inject(this::injectHttpServerRoutes)
- .build()
- .binding()
- .name("http_client0")
- .type("http")
- .kind(CLIENT)
- .exit(isTlsEnabled ? "tls_client0" : "tcp_client0")
- .build()
- .inject(this::injectTlsClient)
- .binding()
- .name("tcp_client0")
- .type("tcp")
- .kind(CLIENT)
- .options(TcpOptionsConfig::builder)
- .host("") // env
- .ports(new int[]{0}) // env
- .build()
- .build()
- .inject(this::injectGuard)
- .inject(this::injectVaults)
- .inject(this::injectCatalog)
- .build()
- .build();
- }
-
- private BindingConfigBuilder injectPlainTcpRoute(
- BindingConfigBuilder binding)
- {
- if (isPlainEnabled)
- {
- binding
- .route()
- .when(TcpConditionConfig::builder)
- .ports(httpPorts)
- .build()
- .exit("http_server0")
- .build();
- }
- return binding;
- }
-
- private BindingConfigBuilder injectTlsTcpRoute(
- BindingConfigBuilder binding)
- {
- if (isTlsEnabled)
- {
- binding
- .route()
- .when(TcpConditionConfig::builder)
- .ports(httpsPorts)
- .build()
- .exit("tls_server0")
- .build();
- }
- return binding;
- }
-
- private NamespaceConfigBuilder injectTlsServer(
- NamespaceConfigBuilder namespace)
- {
- if (isTlsEnabled)
- {
- namespace
- .binding()
- .name("tls_server0")
- .type("tls")
- .kind(SERVER)
- .options(TlsOptionsConfig::builder)
- .keys(List.of("")) // env
- .sni(List.of("")) // env
- .alpn(List.of("")) // env
- .build()
- .vault("server")
- .exit("http_server0")
- .build();
- }
- return namespace;
- }
-
- private HttpOptionsConfigBuilder injectHttpServerOptions(
- HttpOptionsConfigBuilder options)
- {
- if (isJwtEnabled)
- {
- options
- .authorization()
- .name("jwt0")
- .credentials()
- .header()
- .name("authorization")
- .pattern(authorizationHeader)
- .build()
- .build()
- .build();
- }
- return options;
- }
-
- private HttpOptionsConfigBuilder injectHttpServerRequests(
- HttpOptionsConfigBuilder options)
- {
- for (String name : asyncApi.operations.keySet())
- {
- Operation operation = asyncApi.operations.get(name);
- ChannelView channel = ChannelView.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())
- {
- MessageView message = MessageView.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())
- {
- Parameter 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 BindingConfigBuilder injectHttpServerRoutes(
- BindingConfigBuilder binding)
- {
- for (Map.Entry entry : asyncApi.servers.entrySet())
- {
- ServerView server = ServerView.of(entry.getValue());
- for (String name : asyncApi.operations.keySet())
- {
- Operation operation = asyncApi.operations.get(name);
- ChannelView channel = ChannelView.of(asyncApi.channels, operation.channel);
- String path = channel.address().replaceAll("\\{[^}]+\\}", "*");
- String method = operation.bindings.get("http").method;
- binding
- .route()
- .exit("http_client0")
- .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;
- }
-
- private RouteConfigBuilder injectHttpServerRouteGuarded(
- RouteConfigBuilder route,
- ServerView 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("jwt0")
- .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 NamespaceConfigBuilder injectTlsClient(
- NamespaceConfigBuilder namespace)
- {
- if (isTlsEnabled)
- {
- namespace
- .binding()
- .name("tls_client0")
- .type("tls")
- .kind(CLIENT)
- .options(TlsOptionsConfig::builder)
- .trust(List.of("")) // env
- .sni(List.of("")) // env
- .alpn(List.of("")) // env
- .trustcacerts(true)
- .build()
- .vault("client")
- .exit("tcp_client0")
- .build();
- }
- return namespace;
- }
-
- private NamespaceConfigBuilder injectGuard(
- NamespaceConfigBuilder namespace)
- {
- if (isJwtEnabled)
- {
- namespace
- .guard()
- .name("jwt0")
- .type("jwt")
- .options(JwtOptionsConfig::builder)
- .issuer("") // env
- .audience("") // env
- .key()
- .alg("").kty("").kid("").use("").n("").e("").crv("").x("").y("") // env
- .build()
- .build()
- .build();
- }
- return namespace;
- }
-
- private NamespaceConfigBuilder injectVaults(
- NamespaceConfigBuilder namespace)
- {
- if (isTlsEnabled)
- {
- namespace
- .vault()
- .name("client")
- .type("filesystem")
- .options(FileSystemOptionsConfig::builder)
- .trust()
- .store("") // env
- .type("") // env
- .password("") // env
- .build()
- .build()
- .build()
- .vault()
- .name("server")
- .type("filesystem")
- .options(FileSystemOptionsConfig::builder)
- .keys()
- .store("") // env
- .type("") // env
- .password("") //env
- .build()
- .build()
- .build();
- }
- return namespace;
- }
-
- private JsonPatch createEnvVarsPatch()
- {
- JsonPatchBuilder patch = Json.createPatchBuilder();
- patch.replace("/bindings/tcp_client0/options/host", "${{env.TCP_CLIENT_HOST}}");
- patch.replace("/bindings/tcp_client0/options/port", "${{env.TCP_CLIENT_PORT}}");
-
- if (isJwtEnabled)
- {
- // jwt0 guard
- patch.replace("/guards/jwt0/options/issuer", "${{env.JWT_ISSUER}}");
- patch.replace("/guards/jwt0/options/audience", "${{env.JWT_AUDIENCE}}");
- patch.replace("/guards/jwt0/options/keys/0/alg", "${{env.JWT_ALG}}");
- patch.replace("/guards/jwt0/options/keys/0/kty", "${{env.JWT_KTY}}");
- patch.replace("/guards/jwt0/options/keys/0/kid", "${{env.JWT_KID}}");
- patch.replace("/guards/jwt0/options/keys/0/use", "${{env.JWT_USE}}");
- patch.replace("/guards/jwt0/options/keys/0/n", "${{env.JWT_N}}");
- patch.replace("/guards/jwt0/options/keys/0/e", "${{env.JWT_E}}");
- patch.replace("/guards/jwt0/options/keys/0/crv", "${{env.JWT_CRV}}");
- patch.replace("/guards/jwt0/options/keys/0/x", "${{env.JWT_X}}");
- patch.replace("/guards/jwt0/options/keys/0/y", "${{env.JWT_Y}}");
- }
-
- if (isTlsEnabled)
- {
- // tls_server0 binding
- patch.replace("/bindings/tls_server0/options/keys/0", "${{env.TLS_SERVER_KEY}}");
- patch.replace("/bindings/tls_server0/options/sni/0", "${{env.TLS_SERVER_SNI}}");
- patch.replace("/bindings/tls_server0/options/alpn/0", "${{env.TLS_SERVER_ALPN}}");
- // tls_client0 binding
- patch.replace("/bindings/tls_client0/options/trust/0", "${{env.TLS_CLIENT_TRUST}}");
- patch.replace("/bindings/tls_client0/options/sni/0", "${{env.TLS_CLIENT_SNI}}");
- patch.replace("/bindings/tls_client0/options/alpn/0", "${{env.TLS_CLIENT_ALPN}}");
- // client vault
- patch.replace("/vaults/client/options/trust/store", "${{env.TRUSTSTORE_PATH}}");
- patch.replace("/vaults/client/options/trust/type", "${{env.TRUSTSTORE_TYPE}}");
- patch.replace("/vaults/client/options/trust/password", "${{env.TRUSTSTORE_PASSWORD}}");
- // server vault
- patch.replace("/vaults/server/options/keys/store", "${{env.KEYSTORE_PATH}}");
- patch.replace("/vaults/server/options/keys/type", "${{env.KEYSTORE_TYPE}}");
- patch.replace("/vaults/server/options/keys/password", "${{env.KEYSTORE_PASSWORD}}");
- }
-
- return patch.build();
- }
-
- private List unquotedEnvVars()
- {
- return List.of("TCP_CLIENT_PORT");
- }
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/AsyncApi.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/AsyncApi.java
deleted file mode 100644
index 3379ddefd8..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/AsyncApi.java
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import java.util.Map;
-
-public class AsyncApi
-{
- public Map servers;
- public Map channels;
- public Map operations;
- public Components components;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Binding.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Binding.java
deleted file mode 100644
index 0e15d732e4..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Binding.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-public class Binding
-{
- public String method;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Channel.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Channel.java
deleted file mode 100644
index 29ed4bc9e0..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Channel.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import java.util.LinkedHashMap;
-
-import jakarta.json.bind.annotation.JsonbProperty;
-
-public class Channel
-{
- public String address;
- public LinkedHashMap messages;
- public LinkedHashMap parameters;
-
- @JsonbProperty("$ref")
- public String ref;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Components.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Components.java
deleted file mode 100644
index 9288e3e2a6..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Components.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import java.util.Map;
-
-import io.aklivity.zilla.runtime.command.generate.internal.openapi.model.SecurityScheme;
-
-public class Components
-{
- public Map securitySchemes;
- public Map messages;
- public Map schemas;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Item.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Item.java
deleted file mode 100644
index 42ed3d6c2d..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Item.java
+++ /dev/null
@@ -1,21 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-public class Item
-{
- public String type;
- public String description;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Message.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Message.java
deleted file mode 100644
index 18011a28f6..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Message.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import jakarta.json.bind.annotation.JsonbProperty;
-
-public class Message
-{
- public Schema headers;
- public String contentType;
-
- @JsonbProperty("$ref")
- public String ref;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Operation.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Operation.java
deleted file mode 100644
index 89551a6cc0..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Operation.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import java.util.Map;
-
-public class Operation
-{
- public Map bindings;
- public Channel channel;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Parameter.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Parameter.java
deleted file mode 100644
index 7048bf9784..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Parameter.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-public class Parameter
-{
- public Schema schema;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Schema.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Schema.java
deleted file mode 100644
index be52cda60e..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Schema.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import java.util.List;
-import java.util.Map;
-
-import jakarta.json.bind.annotation.JsonbProperty;
-
-public class Schema
-{
- public String type;
- public Schema items;
- public Map properties;
- public List required;
-
- @JsonbProperty("$ref")
- public String ref;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/SecurityScheme.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/SecurityScheme.java
deleted file mode 100644
index 137c290af5..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/SecurityScheme.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-public class SecurityScheme
-{
- public String bearerFormat;
-}
diff --git a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Server.java b/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Server.java
deleted file mode 100644
index 73e0eec68c..0000000000
--- a/incubator/command-generate/src/main/java/io/aklivity/zilla/runtime/command/generate/internal/asyncapi/model/Server.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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.command.generate.internal.asyncapi.model;
-
-import java.util.List;
-import java.util.Map;
-
-public class Server
-{
- public String host;
- public List
-
- ${project.groupId}
- command-generate
- ${project.version}
-
diff --git a/manager/pom.xml b/manager/pom.xml
index b35ea225b9..d68c61b0e2 100644
--- a/manager/pom.xml
+++ b/manager/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
zilla
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/pom.xml b/pom.xml
index 5634a743ef..1e79eab3d8 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
4.0.0
io.aklivity.zilla
zilla
- 0.9.79
+ 0.9.80
pom
zilla
https://github.com/aklivity/zilla
@@ -45,9 +45,9 @@
io/aklivity/zilla/conf/checkstyle/configuration.xml
io/aklivity/zilla/conf/checkstyle/suppressions.xml
4.13.0
- 1.6.0
+ 1.21.1
1.7.36
- 5.10.1
+ 5.10.2
4.0.22
2.6.0
5.8.0
diff --git a/runtime/binding-asyncapi/pom.xml b/runtime/binding-asyncapi/pom.xml
index 8f5cbd55b3..9967789f3e 100644
--- a/runtime/binding-asyncapi/pom.xml
+++ b/runtime/binding-asyncapi/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java
index 9f026a3355..e8b130ec05 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiBindingConfig.java
@@ -47,6 +47,7 @@
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.binding.asyncapi.internal.view.AsyncapiServerView;
import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler;
import io.aklivity.zilla.runtime.engine.config.BindingConfig;
import io.aklivity.zilla.runtime.engine.config.KindConfig;
@@ -222,39 +223,55 @@ private void attachProxyBinding(
(existingValue, newValue) -> existingValue,
Object2ObjectHashMap::new));
+ namespaceGenerator.init(binding);
final NamespaceConfig composite = namespaceGenerator.generateProxy(binding, asyncapis, schemaIdsByApiId::get);
composite.readURL = binding.readURL;
attach.accept(composite);
- for (AsyncapiSchemaConfig config : configs)
- {
- Asyncapi asyncapi = config.asyncapi;
- updateNamespace(config, composite, asyncapi);
- }
+ updateNamespace(configs, composite, new ArrayList<>(asyncapis.values()));
}
private void attachServerClientBinding(
BindingConfig binding,
List configs)
{
+ final Map namespaceConfigs = new HashMap<>();
for (AsyncapiSchemaConfig config : configs)
{
+ namespaceGenerator.init(binding);
Asyncapi asyncapi = config.asyncapi;
- final NamespaceConfig composite = namespaceGenerator.generate(binding, asyncapi);
+ final List servers =
+ namespaceGenerator.filterAsyncapiServers(asyncapi, options.asyncapis.stream()
+ .filter(a -> a.apiLabel.equals(config.apiLabel))
+ .flatMap(a -> a.servers.stream())
+ .collect(Collectors.toList()));
+
+ servers.stream().collect(Collectors.groupingBy(AsyncapiServerView::getPort)).forEach((k, v) ->
+ namespaceConfigs.computeIfAbsent(k, s -> new AsyncapiNamespaceConfig()).addSpecForNamespace(v, config, asyncapi));
+ }
+
+ for (AsyncapiNamespaceConfig namespaceConfig : namespaceConfigs.values())
+ {
+ namespaceConfig.servers.forEach(s -> s.setAsyncapiProtocol(
+ namespaceGenerator.resolveProtocol(s.protocol(), options, namespaceConfig.asyncapis, namespaceConfig.servers)));
+ final NamespaceConfig composite = namespaceGenerator.generate(binding, namespaceConfig);
composite.readURL = binding.readURL;
attach.accept(composite);
- updateNamespace(config, composite, asyncapi);
+ updateNamespace(namespaceConfig.configs, composite, namespaceConfig.asyncapis);
}
}
private void updateNamespace(
- AsyncapiSchemaConfig config,
+ List configs,
NamespaceConfig composite,
- Asyncapi asyncapi)
+ List asyncapis)
{
- composites.put(config.schemaId, composite);
- schemaIdsByApiId.put(config.apiLabel, config.schemaId);
- extractChannels(asyncapi);
- extractOperations(asyncapi);
+ configs.forEach(c ->
+ {
+ composites.put(c.schemaId, composite);
+ schemaIdsByApiId.put(c.apiLabel, c.schemaId);
+ });
+ asyncapis.forEach(this::extractChannels);
+ asyncapis.forEach(this::extractOperations);
}
private void extractNamespace(
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiClientNamespaceGenerator.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiClientNamespaceGenerator.java
index ced6b768af..e1f2908402 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiClientNamespaceGenerator.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiClientNamespaceGenerator.java
@@ -18,10 +18,8 @@
import static java.util.Collections.emptyList;
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.model.Asyncapi;
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.MetricRefConfig;
@@ -32,33 +30,24 @@ public class AsyncapiClientNamespaceGenerator extends AsyncapiNamespaceGenerator
{
public NamespaceConfig generate(
BindingConfig binding,
- Asyncapi asyncapi)
+ AsyncapiNamespaceConfig namespaceConfig)
{
+ List servers = namespaceConfig.servers;
AsyncapiOptionsConfig options = binding.options != null ? (AsyncapiOptionsConfig) binding.options : EMPTY_OPTION;
final List metricRefs = binding.telemetryRef != null ?
binding.telemetryRef.metricRefs : emptyList();
- this.asyncapi = asyncapi;
- this.qname = binding.qname;
- this.namespace = binding.namespace;
- this.qvault = binding.qvault;
- this.vault = binding.vault;
- final List servers =
- filterAsyncapiServers(asyncapi.servers, options.asyncapis.stream()
- .flatMap(a -> a.servers.stream())
- .collect(Collectors.toList()));
- servers.forEach(s -> s.setAsyncapiProtocol(resolveProtocol(s.protocol(), options, servers)));
-
//TODO: keep it until we support different protocols on the same composite binding
AsyncapiServerView serverView = servers.get(0);
this.protocol = serverView.getAsyncapiProtocol();
int[] compositeSecurePorts = resolvePorts(servers, true);
this.isTlsEnabled = compositeSecurePorts.length > 0;
+ final String namespace = String.join("+", namespaceConfig.asyncapiLabels);
return NamespaceConfig.builder()
- .name(String.format("%s.%s", qname, "$composite"))
+ .name(String.format("%s.%s-%s", qname, "$composite", namespace))
.inject(n -> this.injectNamespaceMetric(n, !metricRefs.isEmpty()))
- .inject(n -> this.injectCatalog(n, asyncapi))
+ .inject(n -> this.injectCatalog(n, namespaceConfig.asyncapis))
.inject(n -> protocol.injectProtocolClientCache(n, metricRefs))
.binding()
.name(String.format("%s_client0", protocol.scheme))
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiHttpProtocol.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiHttpProtocol.java
index 271be8235e..5f959d2628 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiHttpProtocol.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiHttpProtocol.java
@@ -14,6 +14,7 @@
*/
package io.aklivity.zilla.runtime.binding.asyncapi.internal.config;
+import static io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiNamespaceGenerator.APPLICATION_JSON;
import static io.aklivity.zilla.runtime.binding.http.config.HttpPolicyConfig.CROSS_ORIGIN;
import java.util.List;
@@ -24,9 +25,9 @@
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.AsyncapiSchema;
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;
@@ -35,23 +36,33 @@
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.DoubleModelConfig;
+import io.aklivity.zilla.runtime.model.core.config.FloatModelConfig;
import io.aklivity.zilla.runtime.model.core.config.Int32ModelConfig;
+import io.aklivity.zilla.runtime.model.core.config.Int64ModelConfig;
import io.aklivity.zilla.runtime.model.core.config.StringModelConfig;
+import io.aklivity.zilla.runtime.model.core.config.StringModelConfigBuilder;
+import io.aklivity.zilla.runtime.model.core.config.StringPattern;
+import io.aklivity.zilla.runtime.model.core.internal.StringModel;
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", Int32ModelConfig.builder().build()
- );
private static final String SCHEME = "http";
private static final String SECURE_PROTOCOL = "https";
+ protected static final Map MODELS = Map.of(
+ "integer", Int32ModelConfig.builder().build(),
+ "integer:int32", Int32ModelConfig.builder().build(),
+ "integer:int64", Int64ModelConfig.builder().build(),
+ "number", FloatModelConfig.builder().build(),
+ "number:float", FloatModelConfig.builder().build(),
+ "number:double", DoubleModelConfig.builder().build()
+ );
+
private static final String SECURE_SCHEME = "https";
private final Map securitySchemes;
private final boolean isJwtEnabled;
@@ -60,11 +71,11 @@ public class AsyncapiHttpProtocol extends AsyncapiProtocol
protected AsyncapiHttpProtocol(
String qname,
- Asyncapi asyncApi,
+ List asyncapis,
AsyncapiOptionsConfig options,
String protocol)
{
- super(qname, asyncApi, protocol, SCHEME);
+ super(qname, asyncapis, protocol, SCHEME);
this.securitySchemes = resolveSecuritySchemes();
this.isJwtEnabled = !securitySchemes.isEmpty();
@@ -91,26 +102,27 @@ public BindingConfigBuilder injectProtocolServerOptions(
public BindingConfigBuilder injectProtocolServerRoutes(
BindingConfigBuilder binding)
{
- for (Map.Entry entry : asyncApi.servers.entrySet())
+ for (Asyncapi asyncapi : asyncapis)
{
- AsyncapiServerView server = AsyncapiServerView.of(entry.getValue());
- for (String name : asyncApi.operations.keySet())
+ for (Map.Entry entry : asyncapi.servers.entrySet())
{
- 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()
+ 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();
+ .build();
+ }
}
}
return binding;
@@ -135,22 +147,25 @@ private HttpOptionsConfigBuilder injectHttpServerOptions(
private HttpOptionsConfigBuilder injectHttpServerRequests(
HttpOptionsConfigBuilder options)
{
- for (String name : asyncApi.operations.keySet())
+ for (Asyncapi asyncapi : asyncapis)
{
- 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())
+ for (String name : asyncapi.operations.keySet())
{
- options
- .request()
- .path(path)
- .method(method)
- .inject(request -> injectContent(request, channel.messages()))
- .inject(request -> injectPathParams(request, channel.parameters()))
+ 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, asyncapi, channel.messages()))
+ .inject(request -> injectPathParams(request, channel.parameters()))
.build();
+ }
}
}
return options;
@@ -158,41 +173,25 @@ private HttpOptionsConfigBuilder injectHttpServerRequests(
private HttpRequestConfigBuilder injectContent(
HttpRequestConfigBuilder request,
+ Asyncapi asyncapi,
Map messages)
{
if (messages != null)
{
- if (hasJsonContentType())
+ if (hasJsonContentType(asyncapi))
{
request.
content(JsonModelConfig::builder)
- .catalog()
- .name(INLINE_CATALOG_NAME)
- .inject(catalog -> injectSchemas(catalog, messages))
- .build()
- .build();
+ .catalog()
+ .name(INLINE_CATALOG_NAME)
+ .inject(cataloged -> injectJsonSchemas(cataloged, asyncapi, messages, APPLICATION_JSON))
+ .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)
@@ -202,9 +201,25 @@ private HttpRequestConfigBuilder injectPathParams(
for (String name : parameters.keySet())
{
AsyncapiParameter parameter = parameters.get(name);
- if (parameter.schema != null && parameter.schema.type != null)
+ AsyncapiSchema schema = parameter.schema;
+ if (schema != null && schema.type != null)
{
- ModelConfig model = MODELS.get(parameter.schema.type);
+ String format = schema.format;
+ String type = schema.type;
+ ModelConfig model;
+ if (StringModel.NAME.equals(type))
+ {
+ StringModelConfigBuilder builder = StringModelConfig.builder();
+ if (format != null)
+ {
+ builder.pattern(StringPattern.of(format));
+ }
+ model = builder.build();
+ }
+ else
+ {
+ model = MODELS.get(format != null ? String.format("%s:%s", type, format) : type);
+ }
if (model != null)
{
request
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiMqttProtocol.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiMqttProtocol.java
index a362960990..970472f3d4 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiMqttProtocol.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiMqttProtocol.java
@@ -16,6 +16,7 @@
import static io.aklivity.zilla.runtime.binding.asyncapi.internal.config.AsyncapiNamespaceGenerator.APPLICATION_JSON;
+import java.util.List;
import java.util.Map;
import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiOptionsConfig;
@@ -46,12 +47,12 @@ public class AsyncapiMqttProtocol extends AsyncapiProtocol
public AsyncapiMqttProtocol(
String qname,
- Asyncapi asyncApi,
+ List asyncapis,
AsyncapiOptionsConfig options,
String protocol,
String namespace)
{
- super(qname, asyncApi, protocol, SCHEME);
+ super(qname, asyncapis, protocol, SCHEME);
final MqttOptionsConfig mqttOptions = options.mqtt;
this.guardName = mqttOptions != null ? String.format("%s:%s", namespace, mqttOptions.authorization.name) : null;
this.authorization = mqttOptions != null ?
@@ -83,23 +84,26 @@ private MqttOptionsConfigBuilder injectMqttAuthorization(
private MqttOptionsConfigBuilder injectMqttTopicsOptions(
MqttOptionsConfigBuilder options)
{
- for (Map.Entry channelEntry : asyncApi.channels.entrySet())
+ for (Asyncapi asyncapi : asyncapis)
{
- String topic = channelEntry.getValue().address.replaceAll("\\{[^}]+\\}", "#");
- Map messages = channelEntry.getValue().messages;
- if (hasJsonContentType())
+ for (Map.Entry channelEntry : asyncapi.channels.entrySet())
{
- options
- .topic()
- .name(topic)
- .content(JsonModelConfig::builder)
- .catalog()
- .name(INLINE_CATALOG_NAME)
- .inject(cataloged -> injectJsonSchemas(cataloged, messages, APPLICATION_JSON))
+ String topic = channelEntry.getValue().address.replaceAll("\\{[^}]+\\}", "#");
+ Map messages = channelEntry.getValue().messages;
+ if (hasJsonContentType(asyncapi))
+ {
+ options
+ .topic()
+ .name(topic)
+ .content(JsonModelConfig::builder)
+ .catalog()
+ .name(INLINE_CATALOG_NAME)
+ .inject(cataloged -> injectJsonSchemas(cataloged, asyncapi, messages, APPLICATION_JSON))
+ .build()
.build()
- .build()
- .inject(t -> injectMqttUserPropertiesConfig(t, messages))
- .build();
+ .inject(t -> injectMqttUserPropertiesConfig(t, asyncapi, messages))
+ .build();
+ }
}
}
return options;
@@ -107,24 +111,25 @@ private MqttOptionsConfigBuilder injectMqttTopicsOptions(
private MqttTopicConfigBuilder injectMqttUserPropertiesConfig(
MqttTopicConfigBuilder topic,
+ Asyncapi asyncapi,
Map messages)
{
for (Map.Entry messageEntry : messages.entrySet())
{
AsyncapiMessageView message =
- AsyncapiMessageView.of(asyncApi.components.messages, messageEntry.getValue());
+ AsyncapiMessageView.of(asyncapi.components.messages, messageEntry.getValue());
if (message.traits() != null)
{
for (AsyncapiTrait asyncapiTrait : message.traits())
{
- AsyncapiTraitView trait = AsyncapiTraitView.of(asyncApi.components.messageTraits, asyncapiTrait);
+ AsyncapiTraitView trait = AsyncapiTraitView.of(asyncapi.components.messageTraits, asyncapiTrait);
for (Map.Entry header : trait.commonHeaders().properties.entrySet())
{
topic
.userProperty()
- .inject(u -> injectUserProperty(u, header.getKey()))
+ .inject(u -> injectUserProperty(u, INLINE_CATALOG_NAME, header.getKey()))
.build();
}
}
@@ -136,13 +141,14 @@ private MqttTopicConfigBuilder injectMqttUserPropertiesConfig(
private MqttUserPropertyConfigBuilder injectUserProperty(
MqttUserPropertyConfigBuilder userProperty,
+ String catalogName,
String subject)
{
userProperty
.name(subject)
.value(JsonModelConfig::builder)
.catalog()
- .name(INLINE_CATALOG_NAME)
+ .name(catalogName)
.schema()
.version(VERSION_LATEST)
.subject(subject)
@@ -156,23 +162,26 @@ private MqttUserPropertyConfigBuilder injectUserProperty(
public BindingConfigBuilder injectProtocolServerRoutes(
BindingConfigBuilder binding)
{
- for (Map.Entry entry : asyncApi.channels.entrySet())
+ for (Asyncapi asyncapi : asyncapis)
{
- String topic = entry.getValue().address.replaceAll("\\{[^}]+\\}", "#");
- binding
- .route()
- .when(MqttConditionConfig::builder)
- .publish()
- .topic(topic)
+ for (Map.Entry entry : asyncapi.channels.entrySet())
+ {
+ String topic = entry.getValue().address.replaceAll("\\{[^}]+\\}", "#");
+ binding
+ .route()
+ .when(MqttConditionConfig::builder)
+ .publish()
+ .topic(topic)
+ .build()
.build()
- .build()
- .when(MqttConditionConfig::builder)
- .subscribe()
- .topic(topic)
+ .when(MqttConditionConfig::builder)
+ .subscribe()
+ .topic(topic)
+ .build()
.build()
- .build()
- .exit(qname)
- .build();
+ .exit(qname)
+ .build();
+ }
}
return binding;
}
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceConfig.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceConfig.java
new file mode 100644
index 0000000000..e725677450
--- /dev/null
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceConfig.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import io.aklivity.zilla.runtime.binding.asyncapi.config.AsyncapiSchemaConfig;
+import io.aklivity.zilla.runtime.binding.asyncapi.internal.model.Asyncapi;
+import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView;
+
+class AsyncapiNamespaceConfig
+{
+ List asyncapiLabels;
+ List servers;
+ List asyncapis;
+ List configs;
+
+ AsyncapiNamespaceConfig()
+ {
+ asyncapiLabels = new ArrayList<>();
+ servers = new ArrayList<>();
+ asyncapis = new ArrayList<>();
+ configs = new ArrayList<>();
+ }
+
+ public void addSpecForNamespace(
+ List servers,
+ AsyncapiSchemaConfig config,
+ Asyncapi asyncapi)
+ {
+ this.asyncapiLabels.add(config.apiLabel);
+ this.servers.addAll(servers);
+ this.configs.add(config);
+ this.asyncapis.add(asyncapi);
+ }
+}
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceGenerator.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceGenerator.java
index 45dfa42ed5..ddf75ede2c 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceGenerator.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiNamespaceGenerator.java
@@ -65,7 +65,6 @@ public abstract class AsyncapiNamespaceGenerator
protected static final Pattern VARIABLE = Pattern.compile("\\{([^}]*.?)\\}");
protected final Matcher variable = VARIABLE.matcher("");
- protected Asyncapi asyncapi;
protected Map asyncapis;
protected boolean isTlsEnabled;
protected AsyncapiProtocol protocol;
@@ -74,9 +73,18 @@ public abstract class AsyncapiNamespaceGenerator
protected String qvault;
protected String vault;
+ public void init(
+ BindingConfig binding)
+ {
+ this.qname = binding.qname;
+ this.namespace = binding.namespace;
+ this.qvault = binding.qvault;
+ this.vault = binding.vault;
+ }
+
public NamespaceConfig generate(
BindingConfig binding,
- Asyncapi asyncapi)
+ AsyncapiNamespaceConfig namespaceConfig)
{
return null;
}
@@ -92,6 +100,7 @@ public NamespaceConfig generateProxy(
protected AsyncapiProtocol resolveProtocol(
String protocolName,
AsyncapiOptionsConfig options,
+ List asyncapis,
List servers)
{
Pattern pattern = Pattern.compile("(http|mqtt|kafka)");
@@ -102,14 +111,14 @@ protected AsyncapiProtocol resolveProtocol(
switch (matcher.group())
{
case "http":
- protocol = new AsyncapiHttpProtocol(qname, asyncapi, options, protocolName);
+ protocol = new AsyncapiHttpProtocol(qname, asyncapis, options, protocolName);
break;
case "mqtt":
- protocol = new AsyncapiMqttProtocol(qname, asyncapi, options, protocolName, namespace);
+ protocol = new AsyncapiMqttProtocol(qname, asyncapis, options, protocolName, namespace);
break;
case "kafka":
case "kafka-secure":
- protocol = new AyncapiKafkaProtocol(qname, asyncapi, servers, options, protocolName);
+ protocol = new AyncapiKafkaProtocol(qname, asyncapis, servers, options, protocolName);
break;
}
}
@@ -121,9 +130,10 @@ protected AsyncapiProtocol resolveProtocol(
}
protected List filterAsyncapiServers(
- Map servers,
+ Asyncapi asyncapi,
List serverConfigs)
{
+ final Map servers = asyncapi.servers;
List filtered;
Map serverViews = servers.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey, e -> AsyncapiServerView.of(e.getValue(), asyncapi.components.serverVariables)));
@@ -178,9 +188,11 @@ public int[] resolvePorts(
protected NamespaceConfigBuilder injectCatalog(
NamespaceConfigBuilder namespace,
- Asyncapi asyncapi)
+ List asyncapis)
{
- if (asyncapi.components != null && asyncapi.components.schemas != null && !asyncapi.components.schemas.isEmpty())
+ final boolean injectCatalog = asyncapis.stream()
+ .anyMatch(a -> a.components != null && a.components.schemas != null && !a.components.schemas.isEmpty());
+ if (injectCatalog)
{
namespace
.catalog()
@@ -188,7 +200,7 @@ protected NamespaceConfigBuilder injectCatalog(
.type(INLINE_CATALOG_TYPE)
.options(InlineOptionsConfig::builder)
.subjects()
- .inject(this::injectSubjects)
+ .inject(s -> injectSubjects(s, asyncapis))
.build()
.build()
.build();
@@ -197,41 +209,48 @@ protected NamespaceConfigBuilder injectCatalog(
}
protected InlineSchemaConfigBuilder injectSubjects(
- InlineSchemaConfigBuilder subjects)
+ InlineSchemaConfigBuilder subjects,
+ List asyncapis)
{
- try (Jsonb jsonb = JsonbBuilder.create())
+ for (Asyncapi asyncapi : asyncapis)
{
- 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();
- }
- if (asyncapi.components.messageTraits != null)
+ if (asyncapi.components != null && asyncapi.components.schemas != null && !asyncapi.components.schemas.isEmpty())
{
- for (Map.Entry entry : asyncapi.components.messageTraits.entrySet())
+ try (Jsonb jsonb = JsonbBuilder.create())
{
- entry.getValue().headers.properties.forEach((k, v) ->
+ 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(k)
+ .subject(entry.getKey())
.version(VERSION_LATEST)
- .schema(writeSchemaYaml(jsonb, yaml, v))
- .build());
+ .schema(writeSchemaYaml(jsonb, yaml, schema))
+ .build();
+ }
+ if (asyncapi.components.messageTraits != null)
+ {
+ for (Map.Entry entry : asyncapi.components.messageTraits.entrySet())
+ {
+ entry.getValue().headers.properties.forEach((k, v) ->
+ subjects
+ .subject(k)
+ .version(VERSION_LATEST)
+ .schema(writeSchemaYaml(jsonb, yaml, v))
+ .build());
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ rethrowUnchecked(ex);
}
}
}
- catch (Exception ex)
- {
- rethrowUnchecked(ex);
- }
return subjects;
}
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProtocol.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProtocol.java
index a28f22d3aa..4c9a60f1bb 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProtocol.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProtocol.java
@@ -16,7 +16,6 @@
import static java.util.Objects.requireNonNull;
-import java.net.URI;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -28,7 +27,6 @@
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.AsyncapiSchemaView;
-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.MetricRefConfig;
@@ -42,7 +40,7 @@ public abstract class AsyncapiProtocol
protected static final String VERSION_LATEST = "latest";
protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher("");
- protected Asyncapi asyncApi;
+ protected final List asyncapis;
protected String qname;
protected Map securitySchemes;
protected boolean isJwtEnabled;
@@ -51,12 +49,12 @@ public abstract class AsyncapiProtocol
protected AsyncapiProtocol(
String qname,
- Asyncapi asyncApi,
+ List asyncapis,
String protocol,
String scheme)
{
this.qname = qname;
- this.asyncApi = asyncApi;
+ this.asyncapis = asyncapis;
this.protocol = protocol;
this.scheme = scheme;
this.securitySchemes = resolveSecuritySchemes();
@@ -84,25 +82,25 @@ public BindingConfigBuilder injectProtocolClientOptions(
protected CatalogedConfigBuilder injectJsonSchemas(
CatalogedConfigBuilder cataloged,
+ Asyncapi asyncapi,
Map messages,
String contentType)
{
for (Map.Entry messageEntry : messages.entrySet())
{
AsyncapiMessageView message =
- AsyncapiMessageView.of(asyncApi.components.messages, messageEntry.getValue());
+ AsyncapiMessageView.of(asyncapi.components.messages, messageEntry.getValue());
if (message.payload() != null)
{
- String schema = AsyncapiSchemaView.of(asyncApi.components.schemas, message.payload()).refKey();
+ String schema = AsyncapiSchemaView.of(asyncapi.components.schemas, message.payload()).refKey();
if (message.contentType() != null && message.contentType().equals(contentType) ||
- jsonContentType.reset(asyncApi.defaultContentType).matches())
+ jsonContentType.reset(asyncapi.defaultContentType).matches())
{
cataloged
.schema()
- .version(VERSION_LATEST)
- .subject(schema)
- .build()
- .build();
+ .version(VERSION_LATEST)
+ .subject(schema)
+ .build();
}
else
{
@@ -113,54 +111,41 @@ protected CatalogedConfigBuilder injectJsonSchemas(
return cataloged;
}
- protected boolean hasJsonContentType()
+ protected boolean hasJsonContentType(
+ Asyncapi asyncapi)
{
String contentType = null;
- if (asyncApi.components != null && asyncApi.components.messages != null &&
- !asyncApi.components.messages.isEmpty())
+ if (asyncapi.components != null && asyncapi.components.messages != null &&
+ !asyncapi.components.messages.isEmpty())
{
- AsyncapiMessage firstAsyncapiMessage = asyncApi.components.messages.entrySet().stream()
+ AsyncapiMessage firstAsyncapiMessage = asyncapi.components.messages.entrySet().stream()
.findFirst().get().getValue();
- contentType = AsyncapiMessageView.of(asyncApi.components.messages, firstAsyncapiMessage).contentType();
+ contentType = AsyncapiMessageView.of(asyncapi.components.messages, firstAsyncapiMessage).contentType();
}
- return contentType != null && jsonContentType.reset(contentType).matches() || asyncApi.defaultContentType != null &&
- jsonContentType.reset(asyncApi.defaultContentType).matches();
+ return contentType != null && jsonContentType.reset(contentType).matches() || asyncapi.defaultContentType != null &&
+ jsonContentType.reset(asyncapi.defaultContentType).matches();
}
protected abstract boolean isSecure();
protected Map resolveSecuritySchemes()
{
- requireNonNull(asyncApi);
+ requireNonNull(asyncapis);
Map result = new HashMap<>();
- if (asyncApi.components != null && asyncApi.components.securitySchemes != null)
+ for (Asyncapi asyncapi : asyncapis)
{
- for (String securitySchemeName : asyncApi.components.securitySchemes.keySet())
+ if (asyncapi.components != null && asyncapi.components.securitySchemes != null)
{
- String guardType = asyncApi.components.securitySchemes.get(securitySchemeName).bearerFormat;
- //TODO: change when jwt support added for mqtt in asyncapi
- //if ("jwt".equals(guardType))
- //{
- // result.put(securitySchemeName, guardType);
- //}
- result.put(securitySchemeName, guardType);
- }
- }
- return result;
- }
-
- 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;
+ for (String securitySchemeName : asyncapi.components.securitySchemes.keySet())
+ {
+ String guardType = asyncapi.components.securitySchemes.get(securitySchemeName).bearerFormat;
+ //TODO: change when jwt support added for mqtt in asyncapi
+ //if ("jwt".equals(guardType))
+ //{
+ // result.put(securitySchemeName, guardType);
+ //}
+ result.put(securitySchemeName, guardType);
+ }
}
}
return result;
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProxyNamespaceGenerator.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProxyNamespaceGenerator.java
index 67d2a8518c..79cb502d77 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProxyNamespaceGenerator.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiProxyNamespaceGenerator.java
@@ -54,7 +54,7 @@ public NamespaceConfig generateProxy(
.map(r -> new AsyncapiRouteConfig(r, resolveApiId))
.collect(Collectors.toList());
this.asyncapis = asyncapis;
- this.qname = binding.qname;
+
final List metricRefs = binding.telemetryRef != null ?
binding.telemetryRef.metricRefs : emptyList();
@@ -130,14 +130,15 @@ public BindingConfigBuilder injectMqttKafkaRoutes(
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("\\{[^}]+\\}", "+");
+ String topic = channel.address();
+
routeBuilder
.when(MqttKafkaConditionConfig::builder)
.topic(topic)
.kind(kind)
.build()
.with(MqttKafkaWithConfig::builder)
- .messages(messages)
+ .messages(messages.replaceAll("\\{([^{}]*)\\}", "\\${params.$1}"))
.build()
.exit(qname);
}
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiServerNamespaceGenerator.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiServerNamespaceGenerator.java
index b8cf008d2c..c58961015a 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiServerNamespaceGenerator.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AsyncapiServerNamespaceGenerator.java
@@ -18,10 +18,8 @@
import static java.util.Collections.emptyList;
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.model.Asyncapi;
import io.aklivity.zilla.runtime.binding.asyncapi.internal.view.AsyncapiServerView;
import io.aklivity.zilla.runtime.binding.tcp.config.TcpConditionConfig;
import io.aklivity.zilla.runtime.binding.tcp.config.TcpOptionsConfig;
@@ -36,31 +34,22 @@ public class AsyncapiServerNamespaceGenerator extends AsyncapiNamespaceGenerator
{
public NamespaceConfig generate(
BindingConfig binding,
- Asyncapi asyncapi)
+ AsyncapiNamespaceConfig namespaceConfig)
{
+ List servers = namespaceConfig.servers;
AsyncapiOptionsConfig options = binding.options != null ? (AsyncapiOptionsConfig) binding.options : EMPTY_OPTION;
final List metricRefs = binding.telemetryRef != null ?
binding.telemetryRef.metricRefs : emptyList();
- this.asyncapi = asyncapi;
- this.qname = binding.qname;
- this.qvault = binding.qvault;
- this.namespace = binding.namespace;
-
- final List servers =
- filterAsyncapiServers(asyncapi.servers, options.asyncapis.stream()
- .flatMap(a -> a.servers.stream())
- .collect(Collectors.toList()));
- servers.forEach(s -> s.setAsyncapiProtocol(resolveProtocol(s.protocol(), options, servers)));
-
//TODO: keep it until we support different protocols on the same composite binding
AsyncapiServerView serverView = servers.get(0);
this.protocol = serverView.getAsyncapiProtocol();
+ final String namespace = String.join("+", namespaceConfig.asyncapiLabels);
return NamespaceConfig.builder()
- .name(String.format("%s/%s", qname, protocol.scheme))
+ .name(String.format("%s/%s", qname, namespace))
.inject(n -> this.injectNamespaceMetric(n, !metricRefs.isEmpty()))
- .inject(n -> this.injectCatalog(n, asyncapi))
+ .inject(n -> this.injectCatalog(n, namespaceConfig.asyncapis))
.inject(n -> injectTcpServer(n, servers, options, metricRefs))
.inject(n -> injectTlsServer(n, options))
.binding()
@@ -68,8 +57,8 @@ public NamespaceConfig generate(
.type(protocol.scheme)
.inject(b -> this.injectMetrics(b, metricRefs, protocol.scheme))
.kind(SERVER)
- .inject(protocol::injectProtocolServerOptions)
- .inject(protocol::injectProtocolServerRoutes)
+ .inject(b -> protocol.injectProtocolServerOptions(b))
+ .inject(b -> protocol.injectProtocolServerRoutes(b))
.build()
.build();
}
diff --git a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AyncapiKafkaProtocol.java b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AyncapiKafkaProtocol.java
index 7929d58b1b..dbe6cd174b 100644
--- a/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AyncapiKafkaProtocol.java
+++ b/runtime/binding-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/config/AyncapiKafkaProtocol.java
@@ -14,6 +14,7 @@
*/
package io.aklivity.zilla.runtime.binding.asyncapi.internal.config;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
@@ -50,12 +51,12 @@ public class AyncapiKafkaProtocol extends AsyncapiProtocol
public AyncapiKafkaProtocol(
String qname,
- Asyncapi asyncApi,
+ List asyncapis,
List servers,
AsyncapiOptionsConfig options,
String protocol)
{
- super(qname, asyncApi, protocol, SCHEME);
+ super(qname, asyncapis, protocol, SCHEME);
this.servers = servers;
this.sasl = options.kafka != null ? options.kafka.sasl : null;
}
@@ -145,21 +146,24 @@ private KafkaOptionsConfigBuilder injectKafkaServerOptions(
private KafkaOptionsConfigBuilder injectKafkaTopicOptions(
KafkaOptionsConfigBuilder options)
{
- for (String name : asyncApi.operations.keySet())
+ for (Asyncapi asyncapi : asyncapis)
{
- 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())
+ for (String name : asyncapi.operations.keySet())
{
- options
- .topic(KafkaTopicConfig::builder)
- .name(topic)
- .inject(topicConfig -> injectValue(topicConfig, channel.messages()))
- .build()
- .build();
+ 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, asyncapi, channel.messages()))
+ .build()
+ .build();
+ }
}
}
return options;
@@ -168,24 +172,30 @@ private KafkaOptionsConfigBuilder injectKafkaTopicOptions(
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()));
+ List bootstrap = new ArrayList<>();
+ for (Asyncapi asyncapi : asyncapis)
+ {
+ bootstrap.addAll(asyncapi.channels.values().stream()
+ .filter(c -> !PARAMETERIZED_TOPIC_PATTERN.matcher(c.address).find())
+ .map(c -> AsyncapiChannelView.of(asyncapi.channels, c).address()).collect(Collectors.toList()));
+ }
+ return options.bootstrap(bootstrap);
}
private KafkaTopicConfigBuilder injectValue(
KafkaTopicConfigBuilder topic,
+ Asyncapi asyncapi,
Map messages)
{
if (messages != null)
{
- if (hasJsonContentType())
+ if (hasJsonContentType(asyncapi))
{
topic
.value(JsonModelConfig::builder)
.catalog()
.name(INLINE_CATALOG_NAME)
- .inject(catalog -> injectSchemas(catalog, messages))
+ .inject(catalog -> injectSchemas(catalog, asyncapi, messages))
.build()
.build();
}
@@ -195,12 +205,13 @@ private KafkaTopicConfigBuilder injectValue(
private CatalogedConfigBuilder injectSchemas(
CatalogedConfigBuilder catalog,
+ Asyncapi asyncapi,
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());
+ 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()
diff --git a/runtime/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java b/runtime/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java
index 9fb0d52045..6aedafa05e 100644
--- a/runtime/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java
+++ b/runtime/binding-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/asyncapi/internal/stream/server/AsyncapiIT.java
@@ -70,4 +70,26 @@ public void shouldCreatePet() throws Exception
{
k3po.finish();
}
+
+ @Test
+ @Configuration("server.multi.protocol.yaml")
+ @Specification({
+ "${mqtt}/publish.and.subscribe/client",
+ "${asyncapi}/mqtt/publish.and.subscribe/server"
+ })
+ public void shouldPublishAndSubscribeMultipleSpec() throws Exception
+ {
+ k3po.finish();
+ }
+
+ @Test
+ @Configuration("server.multi.protocol.yaml")
+ @Specification({
+ "${http}/create.pet/client",
+ "${asyncapi}/http/create.pet/server"
+ })
+ public void shouldCreatePetMultipleSpec() throws Exception
+ {
+ k3po.finish();
+ }
}
diff --git a/runtime/binding-echo/NOTICE b/runtime/binding-echo/NOTICE
index 08323b88fb..b894dd7e66 100644
--- a/runtime/binding-echo/NOTICE
+++ b/runtime/binding-echo/NOTICE
@@ -12,6 +12,18 @@ 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
+ 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
+ JSON-B 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
+ SnakeYAML under Apache License, Version 2.0
+ zilla::runtime::common under The Apache Software License, Version 2.0
+ zilla::runtime::engine under The Apache Software License, Version 2.0
This project also includes code under copyright of the following entities:
diff --git a/runtime/binding-echo/pom.xml b/runtime/binding-echo/pom.xml
index 59a307226e..30029f82e6 100644
--- a/runtime/binding-echo/pom.xml
+++ b/runtime/binding-echo/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
@@ -41,7 +41,6 @@
${project.groupId}
engine
${project.version}
- provided
${project.groupId}
@@ -158,6 +157,11 @@
test-jar
+
+
+ **/*IT.class
+
+
@@ -216,6 +220,7 @@
org.agrona:agrona
io.aklivity.zilla:engine
+ io.aklivity.zilla:common
org.openjdk.jmh:jmh-core
net.sf.jopt-simple:jopt-simple
org.apache.commons:commons-math3
diff --git a/runtime/binding-echo/src/test/java/io/aklivity/zilla/runtime/binding/echo/internal/bench/EchoHandshakeBM.java b/runtime/binding-echo/src/test/java/io/aklivity/zilla/runtime/binding/echo/internal/bench/EchoHandshakeBM.java
new file mode 100644
index 0000000000..522a9210f2
--- /dev/null
+++ b/runtime/binding-echo/src/test/java/io/aklivity/zilla/runtime/binding/echo/internal/bench/EchoHandshakeBM.java
@@ -0,0 +1,152 @@
+/*
+ * 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.echo.internal.bench;
+
+import static io.aklivity.zilla.runtime.engine.config.KindConfig.SERVER;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import java.io.IOException;
+
+import org.agrona.MutableDirectBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Level;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+import org.openjdk.jmh.infra.Control;
+import org.openjdk.jmh.runner.Runner;
+import org.openjdk.jmh.runner.RunnerException;
+import org.openjdk.jmh.runner.options.Options;
+import org.openjdk.jmh.runner.options.OptionsBuilder;
+
+import io.aklivity.zilla.runtime.binding.echo.internal.types.stream.BeginFW;
+import io.aklivity.zilla.runtime.binding.echo.internal.types.stream.WindowFW;
+import io.aklivity.zilla.runtime.engine.Configuration;
+import io.aklivity.zilla.runtime.engine.binding.BindingContext;
+import io.aklivity.zilla.runtime.engine.binding.BindingFactory;
+import io.aklivity.zilla.runtime.engine.binding.BindingHandler;
+import io.aklivity.zilla.runtime.engine.binding.function.MessageConsumer;
+import io.aklivity.zilla.runtime.engine.config.BindingConfig;
+import io.aklivity.zilla.runtime.engine.config.NamespaceConfig;
+
+@State(Scope.Benchmark)
+@BenchmarkMode(Mode.Throughput)
+@Fork(3)
+@Warmup(iterations = 10, time = 1, timeUnit = SECONDS)
+@Measurement(iterations = 5, time = 1, timeUnit = SECONDS)
+@OutputTimeUnit(SECONDS)
+public class EchoHandshakeBM
+{
+ private static final int BUFFER_SIZE = 1024 * 8;
+
+ private final BeginFW.Builder beginRW = new BeginFW.Builder();
+ private final WindowFW.Builder windowRW = new WindowFW.Builder();
+ private final MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[BUFFER_SIZE]);
+
+ private BindingHandler handler;
+ private Runnable detacher;
+
+ @Setup(Level.Trial)
+ public void init() throws IOException
+ {
+ BindingFactory bindings = BindingFactory.instantiate();
+ BindingContext context = bindings.create("echo", new Configuration())
+ .supply(new EchoWorker());
+
+ NamespaceConfig namespace = NamespaceConfig.builder()
+ .name("echo")
+ .binding()
+ .name("echo_server0")
+ .type("echo")
+ .kind(SERVER)
+ .build()
+ .build();
+
+ BindingConfig binding = namespace.bindings.stream()
+ .filter(b -> "echo_server0".equals(b.name))
+ .findFirst()
+ .get();
+
+ this.handler = context.attach(binding);
+ this.detacher = () -> context.detach(binding);
+ }
+
+ @TearDown(Level.Trial)
+ public void destroy()
+ {
+ detacher.run();
+ }
+
+ @Setup(Level.Iteration)
+ public void reset()
+ {
+ }
+
+ @Benchmark
+ public void handshake(
+ final Control control) throws Exception
+ {
+ final BeginFW begin = beginRW.wrap(writeBuffer, 0, writeBuffer.capacity())
+ .originId(0L)
+ .routedId(0L)
+ .streamId(0L)
+ .sequence(0L)
+ .acknowledge(0L)
+ .maximum(BUFFER_SIZE)
+ .traceId(0L)
+ .authorization(0L)
+ .affinity(0L)
+ .build();
+
+ MessageConsumer sender = MessageConsumer.NOOP;
+ MessageConsumer receiver = handler.newStream(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof(), sender);
+
+ receiver.accept(begin.typeId(), begin.buffer(), begin.offset(), begin.sizeof());
+
+ final WindowFW window = windowRW.wrap(writeBuffer, 0, writeBuffer.capacity())
+ .originId(0L)
+ .routedId(0L)
+ .streamId(0L)
+ .sequence(0L)
+ .acknowledge(0L)
+ .maximum(BUFFER_SIZE)
+ .traceId(0L)
+ .budgetId(0L)
+ .padding(0)
+ .build();
+
+ receiver.accept(window.typeId(), window.buffer(), window.offset(), window.sizeof());
+ }
+
+ public static void main(
+ String[] args) throws RunnerException
+ {
+ Options opt = new OptionsBuilder()
+ .include(EchoHandshakeBM.class.getSimpleName())
+ .forks(0)
+ .build();
+
+ new Runner(opt).run();
+ }
+}
diff --git a/runtime/binding-echo/src/test/java/io/aklivity/zilla/runtime/binding/echo/internal/bench/EchoWorker.java b/runtime/binding-echo/src/test/java/io/aklivity/zilla/runtime/binding/echo/internal/bench/EchoWorker.java
new file mode 100644
index 0000000000..175b20f3cf
--- /dev/null
+++ b/runtime/binding-echo/src/test/java/io/aklivity/zilla/runtime/binding/echo/internal/bench/EchoWorker.java
@@ -0,0 +1,359 @@
+/*
+ * 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.echo.internal.bench;
+
+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 org.agrona.concurrent.UnsafeBuffer;
+
+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.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;
+import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler;
+import io.aklivity.zilla.runtime.engine.concurrent.Signaler;
+import io.aklivity.zilla.runtime.engine.config.BindingConfig;
+import io.aklivity.zilla.runtime.engine.config.ModelConfig;
+import io.aklivity.zilla.runtime.engine.config.NamespaceConfig;
+import io.aklivity.zilla.runtime.engine.event.EventFormatter;
+import io.aklivity.zilla.runtime.engine.guard.GuardHandler;
+import io.aklivity.zilla.runtime.engine.metrics.Metric;
+import io.aklivity.zilla.runtime.engine.model.ConverterHandler;
+import io.aklivity.zilla.runtime.engine.model.ValidatorHandler;
+import io.aklivity.zilla.runtime.engine.poller.PollerKey;
+import io.aklivity.zilla.runtime.engine.vault.VaultHandler;
+
+public class EchoWorker implements EngineContext
+{
+ private static final int BUFFER_SIZE = 1024 * 8;
+ private final MutableDirectBuffer writeBuffer = new UnsafeBuffer(new byte[BUFFER_SIZE]);
+
+ @Override
+ public int index()
+ {
+ return 0;
+ }
+
+ @Override
+ public Signaler signaler()
+ {
+ return null;
+ }
+
+ @Override
+ public int supplyTypeId(
+ String name)
+ {
+ return 0;
+ }
+
+ @Override
+ public long supplyInitialId(
+ long bindingId)
+ {
+ return 0;
+ }
+
+ @Override
+ public long supplyReplyId(
+ long initialId)
+ {
+ return 0;
+ }
+
+ @Override
+ public long supplyPromiseId(
+ long initialId)
+ {
+ return 0;
+ }
+
+ @Override
+ public long supplyAuthorizedId()
+ {
+ return 0;
+ }
+
+ @Override
+ public long supplyBudgetId()
+ {
+ return 0;
+ }
+
+ @Override
+ public long supplyTraceId()
+ {
+ return 0;
+ }
+
+ @Override
+ public MessageConsumer supplySender(
+ long streamId)
+ {
+ return null;
+ }
+
+ @Override
+ public MessageConsumer supplyReceiver(
+ long streamId)
+ {
+ return null;
+ }
+
+ @Override
+ public EventFormatter supplyEventFormatter()
+ {
+ return null;
+ }
+
+ @Override
+ public void attachComposite(NamespaceConfig composite)
+ {
+
+ }
+
+ @Override
+ public void detachComposite(
+ NamespaceConfig composite)
+ {
+
+ }
+
+ @Override
+ public void detachSender(long replyId)
+ {
+
+ }
+
+ @Override
+ public void detachStreams(long bindingId)
+ {
+
+ }
+
+ @Override
+ public BudgetCreditor creditor()
+ {
+ return null;
+ }
+
+ @Override
+ public BudgetDebitor supplyDebitor(long budgetId)
+ {
+ return null;
+ }
+
+ @Override
+ public MutableDirectBuffer writeBuffer()
+ {
+ return writeBuffer;
+ }
+
+ @Override
+ public BufferPool bufferPool()
+ {
+ return null;
+ }
+
+ @Override
+ public LongSupplier supplyCounter(
+ long bindingId,
+ long metricId)
+ {
+ return null;
+ }
+
+ @Override
+ public LongSupplier supplyGauge(
+ long bindingId,
+ long metricId)
+ {
+ return null;
+ }
+
+ @Override
+ public LongSupplier[] supplyHistogram(
+ long bindingId,
+ long metricId)
+ {
+ return new LongSupplier[0];
+ }
+
+ @Override
+ public MessageConsumer droppedFrameHandler()
+ {
+ return null;
+ }
+
+ @Override
+ public int supplyClientIndex(
+ long streamId)
+ {
+ return 0;
+ }
+
+ @Override
+ public InetAddress[] resolveHost(
+ String host)
+ {
+ return new InetAddress[0];
+ }
+
+ @Override
+ public PollerKey supplyPollerKey(
+ SelectableChannel channel)
+ {
+ return null;
+ }
+
+ @Override
+ public long supplyBindingId(
+ NamespaceConfig namespace,
+ BindingConfig binding)
+ {
+ return 0;
+ }
+
+ @Override
+ public String supplyNamespace(
+ long namespacedId)
+ {
+ return "";
+ }
+
+ @Override
+ public String supplyLocalName(
+ long namespacedId)
+ {
+ return "";
+ }
+
+ @Override
+ public String supplyQName(
+ long namespacedId)
+ {
+ return "";
+ }
+
+ @Override
+ public int supplyEventId(
+ String name)
+ {
+ return 0;
+ }
+
+ @Override
+ public BindingHandler streamFactory()
+ {
+ return null;
+ }
+
+ @Override
+ public GuardHandler supplyGuard(
+ long guardId)
+ {
+ return null;
+ }
+
+ @Override
+ public VaultHandler supplyVault(
+ long vaultId)
+ {
+ return null;
+ }
+
+ @Override
+ public CatalogHandler supplyCatalog(
+ long catalogId)
+ {
+ return null;
+ }
+
+ @Override
+ public ValidatorHandler supplyValidator(
+ ModelConfig config)
+ {
+ return null;
+ }
+
+ @Override
+ public ConverterHandler supplyReadConverter(
+ ModelConfig config)
+ {
+ return null;
+ }
+
+ @Override
+ public ConverterHandler supplyWriteConverter(
+ ModelConfig config)
+ {
+ return null;
+ }
+
+ @Override
+ public URL resolvePath(
+ String path)
+ {
+ return null;
+ }
+
+ @Override
+ public Metric resolveMetric(
+ String name)
+ {
+ return null;
+ }
+
+ @Override
+ public void onExporterAttached(
+ long exporterId)
+ {
+
+ }
+
+ @Override
+ public void onExporterDetached(
+ long exporterId)
+ {
+
+ }
+
+ @Override
+ public MessageConsumer supplyEventWriter()
+ {
+ return null;
+ }
+
+ @Override
+ public MessageReader supplyEventReader()
+ {
+ return null;
+ }
+
+ @Override
+ public Clock clock()
+ {
+ return null;
+ }
+}
diff --git a/runtime/binding-fan/pom.xml b/runtime/binding-fan/pom.xml
index 146b79f7a7..472f920a5d 100644
--- a/runtime/binding-fan/pom.xml
+++ b/runtime/binding-fan/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-filesystem/pom.xml b/runtime/binding-filesystem/pom.xml
index 4325d6eb50..928388bef1 100644
--- a/runtime/binding-filesystem/pom.xml
+++ b/runtime/binding-filesystem/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java b/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java
index f1da190ac8..b92aad3bee 100644
--- a/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java
+++ b/runtime/binding-filesystem/src/main/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerFactory.java
@@ -36,6 +36,7 @@
import java.util.function.LongUnaryOperator;
import java.util.function.Supplier;
+import org.agrona.BitUtil;
import org.agrona.DirectBuffer;
import org.agrona.MutableDirectBuffer;
import org.agrona.collections.Long2ObjectHashMap;
@@ -385,9 +386,7 @@ private String calculateTag()
{
final byte[] readArray = readBuffer.byteArray();
int bytesRead = input.read(readArray, 0, readArray.length);
- byte[] content = new byte[bytesRead];
- readBuffer.getBytes(0, content, 0, bytesRead);
- newTag = calculateHash(content);
+ newTag = calculateHash(readArray, 0, Math.max(bytesRead, 0));
}
}
catch (IOException ex)
@@ -412,16 +411,14 @@ private InputStream getInputStream()
}
private String calculateHash(
- byte[] content)
+ byte[] input,
+ int offset,
+ int length)
{
-
- byte[] hash = md5.digest(content);
- StringBuilder sb = new StringBuilder(hash.length * 2);
- for (byte b: hash)
- {
- sb.append(String.format("%02x", b));
- }
- return sb.toString();
+ md5.reset();
+ md5.update(input, offset, length);
+ byte[] hash = md5.digest();
+ return BitUtil.toHex(hash);
}
private void onAppEnd(
@@ -631,12 +628,13 @@ private void flushAppData(
if (replyWin > 0)
{
InputStream input = getInputStream();
- boolean replyClosable = input == null;
- if (input != null)
+ try
{
- try
+ int available = input != null ? input.available() : 0;
+
+ if (available > 0)
{
- int reserved = Math.min(replyWin, input.available() + replyPad);
+ int reserved = Math.min(replyWin, available + replyPad);
int length = Math.max(reserved - replyPad, 0);
if (length > 0 && replyDebIndex != NO_DEBITOR_INDEX && replyDeb != null)
@@ -657,25 +655,25 @@ private void flushAppData(
doAppData(traceId, reserved, payload);
replyBytes += bytesRead;
- replyClosable = replyBytes == attributes.size();
+
+ if (replyBytes == attributes.size())
+ {
+ input.close();
+ input = null;
+ }
}
}
- if (replyClosable)
- {
- input.close();
- doAppEnd(traceId);
- }
}
- catch (IOException ex)
+
+ if (available <= 0 || input == null)
{
- doAppAbort(traceId);
+ doAppEnd(traceId);
}
}
- else
+ catch (IOException ex)
{
- doAppEnd(traceId);
+ doAppAbort(traceId);
}
-
}
}
}
diff --git a/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java b/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java
index f4e4ce46ff..8462fb0975 100644
--- a/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java
+++ b/runtime/binding-filesystem/src/test/java/io/aklivity/zilla/runtime/binding/filesystem/internal/stream/FileSystemServerIT.java
@@ -72,6 +72,16 @@ public void shouldReadFileExtensionDefault() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${app}/read.file.payload.empty/client",
+ })
+ public void shouldReadFilePayloadEmpty() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("server.yaml")
@Specification({
diff --git a/runtime/binding-grpc-kafka/pom.xml b/runtime/binding-grpc-kafka/pom.xml
index f953e4fa1b..3668cf3650 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.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-grpc/pom.xml b/runtime/binding-grpc/pom.xml
index 74ada33109..6264b5cbf4 100644
--- a/runtime/binding-grpc/pom.xml
+++ b/runtime/binding-grpc/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-http-filesystem/pom.xml b/runtime/binding-http-filesystem/pom.xml
index 73c97743f2..07fc55f5a1 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.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-http-kafka/pom.xml b/runtime/binding-http-kafka/pom.xml
index 42ca77af4c..f0b1d07e6c 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.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-http/pom.xml b/runtime/binding-http/pom.xml
index fb4f00e238..168347bf9c 100644
--- a/runtime/binding-http/pom.xml
+++ b/runtime/binding-http/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/EventIT.java b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/EventIT.java
index 06e97df87b..18c735874f 100644
--- a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/EventIT.java
+++ b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7230/server/EventIT.java
@@ -19,7 +19,6 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.rules.RuleChain.outerRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
@@ -51,7 +50,6 @@ public class EventIT
public final TestRule chain = outerRule(engine).around(k3po).around(timeout);
@Test
- @Ignore
@Configuration("server.event.yaml")
@Specification({
"${net}/request.with.headers/client",
diff --git a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/EventIT.java b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/EventIT.java
index 8bf5686dbf..a61817a2ee 100644
--- a/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/EventIT.java
+++ b/runtime/binding-http/src/test/java/io/aklivity/zilla/runtime/binding/http/internal/streams/rfc7540/server/EventIT.java
@@ -19,7 +19,6 @@
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.rules.RuleChain.outerRule;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
@@ -51,7 +50,6 @@ public class EventIT
public final TestRule chain = outerRule(engine).around(k3po).around(timeout);
@Test
- @Ignore
@Configuration("server.event.yaml")
@Specification({
"${net}/connection.headers/client",
diff --git a/runtime/binding-kafka-grpc/pom.xml b/runtime/binding-kafka-grpc/pom.xml
index d5999cd0f4..d6a274923e 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.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-kafka/pom.xml b/runtime/binding-kafka/pom.xml
index 1264e1ac87..898df66436 100644
--- a/runtime/binding-kafka/pom.xml
+++ b/runtime/binding-kafka/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaConfiguration.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaConfiguration.java
index 6786f38ffc..d8d9bdbb1d 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaConfiguration.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/KafkaConfiguration.java
@@ -37,6 +37,7 @@ public class KafkaConfiguration extends Configuration
{
public static final boolean DEBUG = Boolean.getBoolean("zilla.binding.kafka.debug");
public static final boolean DEBUG_PRODUCE = DEBUG || Boolean.getBoolean("zilla.binding.kafka.debug.produce");
+ public static final boolean DEBUG_CONSUMER = DEBUG || Boolean.getBoolean("zilla.binding.kafka.debug.consumer");
public static final String KAFKA_CLIENT_ID_DEFAULT = "zilla";
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerConsumerFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerConsumerFactory.java
index 516e5a4363..bb720677b1 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerConsumerFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaCacheServerConsumerFactory.java
@@ -17,7 +17,9 @@
import java.util.ArrayDeque;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
import java.util.function.Consumer;
import java.util.function.LongFunction;
import java.util.function.LongUnaryOperator;
@@ -26,6 +28,7 @@
import org.agrona.MutableDirectBuffer;
import org.agrona.collections.IntHashSet;
import org.agrona.collections.Object2ObjectHashMap;
+import org.agrona.collections.ObjectHashSet;
import org.agrona.concurrent.UnsafeBuffer;
import io.aklivity.zilla.runtime.binding.kafka.internal.KafkaBinding;
@@ -533,7 +536,8 @@ final class KafkaCacheServerConsumerFanout
private final long routedId;
private final long authorization;
private final ArrayList streams;
- private final Object2ObjectHashMap members;
+ private final Map members;
+ private final ObjectHashSet memberIds;
private final Object2ObjectHashMap partitionsByTopic;
private final Object2ObjectHashMap> consumers;
private final Object2ObjectHashMap assignments;
@@ -578,7 +582,8 @@ private KafkaCacheServerConsumerFanout(
this.groupId = groupId;
this.timeout = timeout;
this.streams = new ArrayList<>();
- this.members = new Object2ObjectHashMap<>();
+ this.memberIds = new ObjectHashSet<>();
+ this.members = new LinkedHashMap<>();
this.partitionsByTopic = new Object2ObjectHashMap<>();
this.consumers = new Object2ObjectHashMap<>();
this.assignments = new Object2ObjectHashMap<>();
@@ -590,8 +595,6 @@ private void onConsumerFanoutStreamOpening(
{
streams.add(stream);
- assert !streams.isEmpty();
-
doConsumerInitialBegin(traceId, stream);
if (KafkaState.initialOpened(state))
@@ -886,7 +889,7 @@ private void onConsumerReplyFlush(
memberId = kafkaGroupFlushEx.memberId().asString();
partitionsByTopic.clear();
- members.clear();
+ memberIds.clear();
kafkaGroupFlushEx.members().forEach(m ->
{
@@ -896,7 +899,8 @@ private void onConsumerReplyFlush(
final String consumerId = kafkaGroupMemberMetadataRO.consumerId().asString();
final String mId = m.id().asString();
- members.put(mId, consumerId);
+ members.putIfAbsent(mId, consumerId);
+ memberIds.add(mId);
groupMetadata.topics().forEach(mt ->
{
@@ -907,6 +911,8 @@ private void onConsumerReplyFlush(
});
}
+ members.entrySet().removeIf(m -> !memberIds.contains(m.getKey()));
+
doPartitionAssignment(traceId, authorization);
}
@@ -941,6 +947,12 @@ private void onConsumerReplyData(
IntHashSet partitions = new IntHashSet();
List topicConsumers = new ArrayList<>();
+ if (KafkaConfiguration.DEBUG_CONSUMER)
+ {
+ System.out.printf("Subscription: MemberId - %s\n", memberId);
+ ta.partitions().forEach(np -> System.out.printf("%s\n", np));
+ }
+
stream.doConsumerReplyData(traceId, flags, replyPad, EMPTY_OCTETS,
ex -> ex.set((b, o, l) -> kafkaDataExRW.wrap(b, o, l)
.typeId(kafkaTypeId)
@@ -1089,7 +1101,6 @@ private void doMemberAssigment(
.wrap(writeBuffer, DataFW.FIELD_OFFSET_PAYLOAD, writeBuffer.capacity());
this.consumers.forEach((k, v) ->
- {
assignmentBuilder.item(ma -> ma
.memberId(k)
.assignments(ta -> v.forEach(tp -> ta.item(i -> i
@@ -1101,12 +1112,19 @@ private void doMemberAssigment(
u.item(ud -> ud
.consumerId(at.consumerId)
.partitions(pt -> at.partitions.forEach(up ->
- pt.item(pi -> pi.partitionId(up))))))))
- ))));
- });
+ pt.item(pi -> pi.partitionId(up)))))))))))));
Array32FW assignment = assignmentBuilder.build();
+ if (KafkaConfiguration.DEBUG_CONSUMER)
+ {
+ assignment.forEach(c ->
+ {
+ System.out.printf("Assignment: MemberId - %s\n", c.memberId());
+ c.assignments().forEach(a -> a.partitions().forEach(System.out::println));
+ });
+ }
+
doConsumerInitialData(traceId, authorization, initialBud, assignment.sizeof(), 3,
assignment.buffer(), assignment.offset(), assignment.sizeof(), EMPTY_OCTETS);
}
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 d5897f2cc2..f68c1fa26d 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
@@ -57,6 +57,7 @@
import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.KafkaBeginExFW;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.KafkaDataExFW;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.KafkaOffsetFetchBeginExFW;
+import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.KafkaOffsetFetchDataExFW;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.KafkaResetExFW;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.ProxyBeginExFW;
import io.aklivity.zilla.runtime.binding.kafka.internal.types.stream.ResetFW;
@@ -711,6 +712,8 @@ private int decodeOffsetFetchError(
client.decodeableResponseBytes -= error.sizeof();
assert client.decodeableResponseBytes >= 0;
+ client.onDecodeEmptyOffsetFetchResponse(traceId);
+
client.decoder = decodeOffsetFetchResponse;
client.nextResponseId++;
}
@@ -1729,6 +1732,20 @@ private void onDecodeOffsetFetchResponse(
}
}
+ private void onDecodeEmptyOffsetFetchResponse(
+ long traceId)
+ {
+ if (topicPartitions.isEmpty())
+ {
+ final KafkaDataExFW kafkaDataEx = kafkaDataExRW.wrap(extBuffer, 0, extBuffer.capacity())
+ .typeId(kafkaTypeId)
+ .offsetFetch(KafkaOffsetFetchDataExFW.Builder::build)
+ .build();
+
+ delegate.doApplicationData(traceId, authorization, kafkaDataEx);
+ }
+ }
+
public void onDecodeTopic(
long traceId)
{
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 385209cc6d..70c89311fc 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
@@ -159,7 +159,7 @@ public final class KafkaClientProduceFactory extends KafkaClientSaslHandshaker i
private final KafkaProduceClientFlusher flushRecord = this::flushRecord;
private final KafkaProduceClientFlusher flushRecordInit = this::flushRecordInit;
- private final KafkaProduceClientFlusher frameProduceRecordContFin = this::flushRecordContFin;
+ private final KafkaProduceClientFlusher flushRecordContFin = this::flushRecordContFin;
private final KafkaProduceClientFlusher flushRecordIgnoreAll = this::flushRecordIgnoreAll;
private final KafkaProduceClientDecoder decodeSaslHandshakeResponse = this::decodeSaslHandshakeResponse;
@@ -546,10 +546,9 @@ private int flushRecordInit(
final KafkaAckMode ackMode = kafkaProduceDataEx.ackMode().get();
final KafkaKeyFW key = kafkaProduceDataEx.key();
final Array32FW headers = kafkaProduceDataEx.headers();
- client.encodeableRecordBytesDeferred = kafkaProduceDataEx.deferred();
- client.valueChecksum = kafkaProduceDataEx.crc32c();
+ final int deferred = kafkaProduceDataEx.deferred();
final int valueSize = payload != null ? payload.sizeof() : 0;
- client.valueCompleteSize = valueSize + client.encodeableRecordBytesDeferred;
+ final int valueCompleteSize = valueSize + deferred;
final int maxEncodeableBytes = client.encodeSlotLimit + client.valueCompleteSize + produceRecordFramingSize;
@@ -562,6 +561,10 @@ private int flushRecordInit(
client.doEncodeRequestIfNecessary(traceId, budgetId);
}
+ client.valueChecksum = kafkaProduceDataEx.crc32c();
+ client.encodeableRecordBytesDeferred = deferred;
+ client.valueCompleteSize = valueCompleteSize;
+
if (client.producerId == RECORD_BATCH_PRODUCER_ID_NONE)
{
client.baseSequence = sequence;
@@ -574,8 +577,7 @@ private int flushRecordInit(
client.doEncodeRecordInit(traceId, timestamp, ackMode, key, payload, headers);
if (client.encodeSlot != NO_SLOT)
{
- client.flusher = frameProduceRecordContFin;
- client.flushFlags = FLAGS_INIT;
+ client.flusher = flushRecordContFin;
}
else
{
@@ -610,6 +612,7 @@ private int flushRecordContFin(
assert progress == limit;
client.flusher = flushRecord;
client.flushFlags = FLAGS_FIN;
+ client.encodeableRecordBytesDeferred = 0;
}
return progress;
@@ -1967,7 +1970,7 @@ private void doEncodeProduceRequest(
final ByteBuffer encodeSlotByteBuffer = encodePool.byteBuffer(encodeSlot);
final int encodeSlotBytePosition = encodeSlotByteBuffer.position();
- final int partialValueSize = flushFlags != FLAGS_FIN ? encodeableRecordValueBytes : 0;
+ final int partialValueSize = encodeableRecordBytesDeferred > 0 ? encodeableRecordValueBytes : 0;
encodeSlotByteBuffer.limit(encodeSlotBytePosition + encodeSlotLimit - partialValueSize);
encodeSlotByteBuffer.position(encodeSlotBytePosition + encodeSlotOffset + crcLimit);
@@ -1976,7 +1979,7 @@ private void doEncodeProduceRequest(
crc.update(encodeSlotByteBuffer);
long checksum = crc.getValue();
- if (flushFlags != FLAGS_FIN)
+ if (encodeableRecordBytesDeferred > 0)
{
checksum = computeChecksum(encodeBuffer, encodeLimit, encodeProgress, encodeSlotBuffer, checksum);
}
diff --git a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java
index 54b8a7892b..9280cfef77 100644
--- a/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java
+++ b/runtime/binding-kafka/src/main/java/io/aklivity/zilla/runtime/binding/kafka/internal/stream/KafkaMergedFactory.java
@@ -1341,7 +1341,7 @@ private void onMergedInitialAbort(
describeStream.doDescribeInitialAbortIfNecessary(traceId);
metaStream.doMetaInitialAbortIfNecessary(traceId);
fetchStreams.forEach(f -> f.onMergedInitialAbort(traceId));
- produceStreams.forEach(f -> f.doProduceInitialEndIfNecessary(traceId));
+ produceStreams.forEach(f -> f.doProduceInitialAbortIfNecessary(traceId));
if (consumerStream != null)
{
@@ -2010,6 +2010,8 @@ private void onTopicOffsetFetchDataChanged(
long traceId,
Array32FW partitions)
{
+ offsetsByPartitionId.clear();
+
partitions.forEach(p -> offsetsByPartitionId.put(p.partitionId(),
new KafkaPartitionOffset(
topic,
@@ -2020,6 +2022,17 @@ private void onTopicOffsetFetchDataChanged(
p.metadata().asString())));
doFetchPartitionsIfNecessary(traceId);
+
+ fetchStreams.removeIf(f ->
+ {
+ boolean missing = !leadersByAssignedId.containsKey(f.partitionId);
+ if (missing)
+ {
+ f.doFetchInitialEndIfNecessary(traceId);
+ f.doFetchReplyResetIfNecessary(traceId);
+ }
+ return missing;
+ });
}
private void doFetchPartitionOffsets(
@@ -2027,6 +2040,7 @@ private void doFetchPartitionOffsets(
{
if (hasFetchCapability(capabilities))
{
+ offsetFetchStream.resetStreamIfNecessary(traceId);
offsetFetchStream.doOffsetFetchInitialBeginIfNecessary(traceId);
}
}
@@ -3190,7 +3204,7 @@ private KafkaUnmergedOffsetFetchStream(
private void doOffsetFetchInitialBeginIfNecessary(
long traceId)
{
- if (!KafkaState.initialOpening(state))
+ if (!KafkaState.initialOpening(state) || KafkaState.closed(state))
{
doOffsetFetchInitialBegin(traceId);
}
@@ -3199,6 +3213,11 @@ private void doOffsetFetchInitialBeginIfNecessary(
private void doOffsetFetchInitialBegin(
long traceId)
{
+ if (KafkaState.closed(state))
+ {
+ state = 0;
+ }
+
assert state == 0;
state = KafkaState.openingInitial(state);
@@ -3332,7 +3351,8 @@ private void onOffsetFetchReplyData(
final Array32FW partitions = kafkaOffsetFetchDataEx.partitions();
merged.onTopicOffsetFetchDataChanged(traceId, partitions);
- doOffsetFetchReplyWindow(traceId, 0, replyMax);
+ doOffsetFetchInitialEndIfNecessary(traceId);
+ doOffsetFetchReplyResetIfNecessary(traceId);
}
}
@@ -3424,6 +3444,16 @@ private void doOffsetFetchReplyReset(
doReset(receiver, merged.routedId, merged.resolvedId, replyId, replySeq, replyAck, replyMax,
traceId, merged.authorization, EMPTY_EXTENSION);
}
+
+ private void resetStreamIfNecessary(
+ long traceId)
+ {
+ if (KafkaState.initialOpening(state))
+ {
+ doOffsetFetchInitialAbortIfNecessary(traceId);
+ doOffsetFetchReplyResetIfNecessary(traceId);
+ }
+ }
}
private final class KafkaUnmergedFetchStream
diff --git a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/cache/KafkaCachePartitionTest.java b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/cache/KafkaCachePartitionTest.java
index 0347028b1c..9c7cf45248 100644
--- a/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/cache/KafkaCachePartitionTest.java
+++ b/runtime/binding-kafka/src/test/java/io/aklivity/zilla/runtime/binding/kafka/internal/cache/KafkaCachePartitionTest.java
@@ -236,8 +236,6 @@ public void shouldCleanSegment() throws Exception
partition.writeEntry(null, 1L, 1L, 12L, entryMark, valueMark, 0L, -1L,
key, headers, value, ancestor, 0x00, KafkaDeltaType.NONE, ConverterHandler.NONE, ConverterHandler.NONE, false);
- partition.writeEntry(null, 1L, 1L, 12L, entryMark, valueMark, 0L, -1L,
- key, headers, value, ancestor, 0x00, KafkaDeltaType.NONE, null, null, false);
Node head15 = partition.append(15L);
KafkaCacheSegment head15s = head15.segment();
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 008850670d..05fda6b413 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
@@ -713,4 +713,24 @@ public void shouldAckMessageOffset() throws Exception
{
k3po.finish();
}
+
+ @Test
+ @Configuration("cache.options.merged.yaml")
+ @Specification({
+ "${app}/merged.fetch.unsubscribe/client",
+ "${app}/unmerged.group.fetch.unsubscribe/server"})
+ public void shouldUnsubscribeOnPartitionReassignment() throws Exception
+ {
+ k3po.finish();
+ }
+
+ @Test
+ @Configuration("cache.options.merged.yaml")
+ @Specification({
+ "${app}/merged.fetch.unsubscribe/client",
+ "${app}unmerged.group.fetch.assignment.incomplete/server"})
+ public void shouldCancelPreviousIncompleteOffsetFetchRequest() throws Exception
+ {
+ k3po.finish();
+ }
}
diff --git a/runtime/binding-mqtt-kafka/pom.xml b/runtime/binding-mqtt-kafka/pom.xml
index f6e06bedae..bec6d7d5f7 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.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java
index ed1d8ada79..99151c0580 100644
--- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java
+++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/config/MqttKafkaRouteConfig.java
@@ -27,14 +27,14 @@
public class MqttKafkaRouteConfig
{
- private final Optional with;
+
+ public final MqttKafkaWithResolver with;
private final List when;
private final LongPredicate authorized;
public final long id;
public final long order;
- public final String16FW messages;
public final String16FW retained;
public MqttKafkaRouteConfig(
@@ -45,12 +45,13 @@ public MqttKafkaRouteConfig(
this.order = route.order;
this.with = Optional.ofNullable(route.with)
.map(MqttKafkaWithConfig.class::cast)
- .map(c -> new MqttKafkaWithResolver(options, c));
- this.messages = with.isPresent() ? with.get().messages() : options.topics.messages;
+ .map(c -> new MqttKafkaWithResolver(options, c))
+ .orElse(new MqttKafkaWithResolver(options, null));
this.retained = options.topics.retained;
this.when = route.when.stream()
.map(MqttKafkaConditionConfig.class::cast)
.map(MqttKafkaConditionMatcher::new)
+ .peek(m -> m.observe(with::onConditionMatched))
.collect(toList());
this.authorized = route.authorized;
}
diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfiguration.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfiguration.java
index 945170aded..2b88be4f92 100644
--- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfiguration.java
+++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfiguration.java
@@ -25,6 +25,7 @@
import org.agrona.LangUtil;
+import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttQoS;
import io.aklivity.zilla.runtime.engine.Configuration;
public class MqttKafkaConfiguration extends Configuration
@@ -39,6 +40,7 @@ public class MqttKafkaConfiguration extends Configuration
public static final IntPropertyDef WILL_STREAM_RECONNECT_DELAY;
public static final BooleanPropertyDef BOOTSTRAP_AVAILABLE;
public static final IntPropertyDef BOOTSTRAP_STREAM_RECONNECT_DELAY;
+ public static final IntPropertyDef PUBLISH_QOS_MAX;
static
{
@@ -57,6 +59,7 @@ public class MqttKafkaConfiguration extends Configuration
WILL_STREAM_RECONNECT_DELAY = config.property("will.stream.reconnect", 2);
BOOTSTRAP_AVAILABLE = config.property("bootstrap.available", true);
BOOTSTRAP_STREAM_RECONNECT_DELAY = config.property("bootstrap.stream.reconnect", 2);
+ PUBLISH_QOS_MAX = config.property("publish.qos.max", 2);
MQTT_KAFKA_CONFIG = config;
}
@@ -116,6 +119,11 @@ public int bootstrapStreamReconnectDelay()
return BOOTSTRAP_STREAM_RECONNECT_DELAY.getAsInt(this);
}
+ public MqttQoS publishQosMax()
+ {
+ return MqttQoS.valueOf(PUBLISH_QOS_MAX.getAsInt(this));
+ }
+
private static StringSupplier decodeStringSupplier(
String fullyQualifiedMethodName)
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 a152f8f2d0..855ee3fdd1 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
@@ -29,6 +29,7 @@
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.MqttQoS;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttTopicFilterFW;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.String16FW;
import io.aklivity.zilla.runtime.engine.config.BindingConfig;
@@ -95,6 +96,12 @@ public List resolveAll(
.collect(Collectors.toList());
}
+ public MqttQoS publishQosMax()
+ {
+ return routes.stream().noneMatch(r -> r.with != null && r.with.containsParams()) ?
+ MqttQoS.EXACTLY_ONCE : MqttQoS.AT_LEAST_ONCE;
+ }
+
public String16FW messagesTopic()
{
return options.topics.messages;
diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcher.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcher.java
index 866ebdc322..7d1af3446c 100644
--- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcher.java
+++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/config/MqttKafkaConditionMatcher.java
@@ -16,6 +16,7 @@
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -24,34 +25,45 @@
public class MqttKafkaConditionMatcher
{
- private final List matchers;
+ private final Matcher matcher;
public final MqttKafkaConditionKind kind;
+ private Consumer observer;
public MqttKafkaConditionMatcher(
MqttKafkaConditionConfig condition)
{
- this.matchers = asTopicMatchers(condition.topics);
+ this.matcher = asTopicMatcher(condition.topics);
this.kind = condition.kind;
}
public boolean matches(
String topic)
{
- boolean match = false;
- if (matchers != null)
+ return this.matcher == null ||
+ this.matcher.reset(topic).matches() && observeMatched();
+ }
+
+ public String parameter(
+ String name)
+ {
+ return matcher.group(name);
+ }
+
+ public void observe(
+ Consumer observer)
+ {
+ this.observer = observer;
+ }
+
+ private boolean observeMatched()
+ {
+ if (observer != null)
{
- for (Matcher matcher : matchers)
- {
- if (matcher.reset(topic).matches())
- {
- match = true;
- break;
- }
- }
+ observer.accept(this);
}
- return match;
- }
+ return true;
+ }
private static List asTopicMatchers(
List wildcards)
@@ -62,11 +74,37 @@ private static List asTopicMatchers(
String patternBegin = wildcard.startsWith("/") ? "(" : "^(?!\\/)(";
String fixedPattern = patternBegin + asRegexPattern(wildcard, 0, true) + ")?\\/?\\#?";
String nonFixedPattern = patternBegin + asRegexPattern(wildcard, 0, false) + ")?\\/?\\#";
+ fixedPattern = fixedPattern.replaceAll("\\{([a-zA-Z_]+)\\}", "(?<$1>.+)");
+ nonFixedPattern = nonFixedPattern.replaceAll("\\{([a-zA-Z_]+)\\}", "");
matchers.add(Pattern.compile(nonFixedPattern + "|" + fixedPattern).matcher(""));
}
return matchers;
}
+ private static Matcher asTopicMatcher(
+ List wildcards)
+ {
+ StringBuilder combinedRegex = new StringBuilder();
+
+ for (String wildcard : wildcards)
+ {
+ String patternBegin = wildcard.startsWith("/") ? "(" : "^(?!\\/)(";
+ String fixedPattern = patternBegin + asRegexPattern(wildcard, 0, true) + ")?\\/?\\#?";
+ String nonFixedPattern = patternBegin + asRegexPattern(wildcard, 0, false) + ")?\\/?\\#";
+ fixedPattern = fixedPattern.replaceAll("\\{([a-zA-Z_]+)\\}", "(?<$1>.+)");
+ nonFixedPattern = nonFixedPattern.replaceAll("\\{([a-zA-Z_]+)\\}", "");
+
+ combinedRegex.append(nonFixedPattern).append("|").append(fixedPattern).append("|");
+ }
+
+ if (combinedRegex.length() > 0)
+ {
+ combinedRegex.deleteCharAt(combinedRegex.length() - 1);
+ }
+
+ return Pattern.compile(combinedRegex.toString()).matcher("");
+ }
+
private static String asRegexPattern(
String wildcard,
int level,
@@ -93,6 +131,7 @@ private static String asRegexPattern(
.replace(".", "\\.")
.replace("$", "\\$")
.replace("+", "[^/]*")
+ .replace("\\{[^}]+\\}", "[^/]*")
.replace("#", ".*");
pattern = (level > 0) ? "(\\/\\+|\\/" + currentPart + ")" : "(\\+|" + currentPart + ")";
}
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 d0ca895a2c..69c4fe4863 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
@@ -14,6 +14,10 @@
*/
package io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.config;
+import java.util.function.Function;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaOptionsConfig;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.config.MqttKafkaWithConfig;
@@ -21,17 +25,51 @@
public class MqttKafkaWithResolver
{
- private final String16FW messages;
+ private static final Pattern PARAMS_PATTERN = Pattern.compile("\\$\\{params\\.([a-zA-Z_]+)\\}");
+
+ private final Matcher paramsMatcher;
+ private final MqttKafkaWithConfig with;
+ private final MqttKafkaOptionsConfig options;
+
+ private Function replacer = r -> null;
public MqttKafkaWithResolver(
MqttKafkaOptionsConfig options,
MqttKafkaWithConfig with)
{
- this.messages = with.messages == null ? options.topics.messages : new String16FW(with.messages);
+ this.paramsMatcher = PARAMS_PATTERN.matcher("");
+ this.with = with;
+ this.options = options;
+ }
+
+ public void onConditionMatched(
+ MqttKafkaConditionMatcher condition)
+ {
+ this.replacer = r -> condition.parameter(r.group(1));
}
- public String16FW messages()
+ public boolean containsParams()
{
- return messages;
+ return with != null && paramsMatcher.reset(with.messages).find();
+ }
+
+ public String16FW resolveMessages()
+ {
+ String topic = null;
+ if (with != null)
+ {
+ topic = with.messages;
+ Matcher topicMatcher = paramsMatcher.reset(topic);
+ StringBuilder result = new StringBuilder();
+ while (topicMatcher.find())
+ {
+ String replacement = replacer.apply(paramsMatcher.toMatchResult());
+ topicMatcher.appendReplacement(result, replacement);
+ }
+ topicMatcher.appendTail(result);
+
+ topic = result.toString();
+ }
+ return topic == null ? options.topics.messages : new String16FW(topic);
}
}
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 2554a12fe6..0ef4c014a0 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
@@ -207,11 +207,17 @@ public MessageConsumer newStream(
binding.resolve(authorization, mqttPublishBeginEx.topic().asString()) : null;
MessageConsumer newStream = null;
- if (resolved != null)
+ final int qos = mqttPublishBeginEx.qos();
+ final int qosMax = binding.publishQosMax().value();
+
+ if (mqttPublishBeginEx.qos() > qosMax)
+ {
+ return null;
+ }
+ else if (resolved != null)
{
final long resolvedId = resolved.id;
- final String16FW messagesTopic = resolved.messages;
- final int qos = mqttPublishBeginEx.qos();
+ final String16FW messagesTopic = resolved.with.resolveMessages();
final MqttPublishProxy proxy = new MqttPublishProxy(mqtt, originId, routedId, initialId, resolvedId, affinity,
binding, messagesTopic, binding.retainedTopic(), qos, binding.clients);
newStream = proxy::onMqttMessage;
diff --git a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishMetadata.java b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishMetadata.java
index 30bd6d28ef..da188b6d2d 100644
--- a/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishMetadata.java
+++ b/runtime/binding-mqtt-kafka/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishMetadata.java
@@ -93,12 +93,14 @@ public static final class KafkaOffsetMetadata
public final IntArrayList packetIds;
public long sequence;
+ public String topic;
KafkaOffsetMetadata(
+ String topic,
long producerId,
short producerEpoch)
{
- this(producerId, producerEpoch, new IntArrayList());
+ this(topic, producerId, producerEpoch, new IntArrayList());
}
KafkaOffsetMetadata(
@@ -111,6 +113,19 @@ public static final class KafkaOffsetMetadata
this.producerEpoch = producerEpoch;
this.packetIds = packetIds;
}
+
+ KafkaOffsetMetadata(
+ String topic,
+ long producerId,
+ short producerEpoch,
+ IntArrayList packetIds)
+ {
+ this.sequence = 1;
+ this.topic = topic.intern();
+ this.producerId = producerId;
+ this.producerEpoch = producerEpoch;
+ this.packetIds = packetIds;
+ }
}
public static final class KafkaOffsetMetadataHelper
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 04d48c1d68..ee699398fc 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
@@ -72,6 +72,7 @@
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttPayloadFormat;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttPayloadFormatFW;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttPublishFlags;
+import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttQoS;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttSessionFlags;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttSessionSignalFW;
import io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.types.MqttSessionStateFW;
@@ -262,6 +263,7 @@ public class MqttKafkaSessionFactory implements MqttKafkaStreamFactory
private final Int2ObjectHashMap qosLevels;
private final Long2ObjectHashMap clientMetadata;
private final KafkaOffsetMetadataHelper offsetMetadataHelper;
+ private final MqttQoS publishQosMax;
private String serverRef;
private int reconnectAttempt;
@@ -303,6 +305,7 @@ public MqttKafkaSessionFactory(
this.sessionExpiryIds = new Object2LongHashMap<>(-1);
this.instanceId = instanceId;
this.reconnectDelay = config.willStreamReconnectDelay();
+ this.publishQosMax = config.publishQosMax();
this.qosLevels = new Int2ObjectHashMap<>();
this.qosLevels.put(0, new String16FW("0"));
this.qosLevels.put(1, new String16FW("1"));
@@ -336,8 +339,10 @@ public MessageConsumer newStream(
{
final long resolvedId = resolved.id;
final String16FW sessionTopic = binding.sessionsTopic();
+
+ final MqttQoS publishQosMax = MqttQoS.valueOf(Math.min(binding.publishQosMax().value(), this.publishQosMax.value()));
final MqttSessionProxy proxy = new MqttSessionProxy(mqtt, originId, routedId, initialId, resolvedId,
- binding.id, sessionTopic);
+ binding.id, sessionTopic, publishQosMax);
newStream = proxy::onMqttMessage;
}
@@ -391,8 +396,8 @@ private final class MqttSessionProxy
private final List offsetFetches;
private final List initializablePartitions;
private final Long2LongHashMap leaderEpochs;
- private final IntArrayQueue unackedPacketIds;
+ private IntArrayQueue unackedPacketIds;
private String lifetimeId;
private KafkaSessionStream session;
private KafkaGroupStream group;
@@ -424,11 +429,12 @@ private final class MqttSessionProxy
private String willId;
private int delay;
private boolean redirect;
- private int publishQosMax;
+ private int publishQosMax = -1;
private int unfetchedKafkaTopics;
private MqttKafkaPublishMetadata metadata;
- private final Set messagesTopics;
private final String16FW retainedTopic;
+
+ private Set messagesTopics;
private long producerId;
private short producerEpoch;
@@ -439,7 +445,8 @@ private MqttSessionProxy(
long initialId,
long resolvedId,
long bindingId,
- String16FW sessionsTopic)
+ String16FW sessionsTopic,
+ MqttQoS publishQosMax)
{
this.mqtt = mqtt;
this.originId = originId;
@@ -457,10 +464,14 @@ private MqttSessionProxy(
final MqttKafkaBindingConfig binding = supplyBinding.apply(bindingId);
final String16FW messagesTopic = binding.messagesTopic();
this.retainedTopic = binding.retainedTopic();
- this.messagesTopics = binding.routes.stream().map(r -> r.messages).collect(Collectors.toSet());
- this.messagesTopics.add(messagesTopic);
- this.unfetchedKafkaTopics = messagesTopics.size() + 1;
- this.unackedPacketIds = new IntArrayQueue();
+ this.publishQosMax = publishQosMax.value();
+ if (publishQosMax == MqttQoS.EXACTLY_ONCE)
+ {
+ this.messagesTopics = binding.routes.stream().map(r -> r.with.resolveMessages()).collect(Collectors.toSet());
+ this.messagesTopics.add(messagesTopic);
+ this.unfetchedKafkaTopics = messagesTopics.size() + 1;
+ this.unackedPacketIds = new IntArrayQueue();
+ }
}
private void onMqttMessage(
@@ -534,7 +545,7 @@ private void onMqttBegin(
sessionExpiryMillis = (int) SECONDS.toMillis(mqttSessionBeginEx.expiry());
sessionFlags = mqttSessionBeginEx.flags();
redirect = hasRedirectCapability(mqttSessionBeginEx.capabilities());
- publishQosMax = mqttSessionBeginEx.publishQosMax();
+ publishQosMax = publishQosMax == -1 ? mqttSessionBeginEx.publishQosMax() : publishQosMax;
if (!isSetWillFlag(sessionFlags) || isSetCleanStart(sessionFlags))
{
@@ -1137,6 +1148,7 @@ private void onSessionBegin(
.flags(sessionFlags)
.expiry((int) TimeUnit.MILLISECONDS.toSeconds(sessionExpiryMillis))
.subscribeQosMax(MQTT_KAFKA_MAX_QOS)
+ .publishQosMax(publishQosMax)
.capabilities(MQTT_KAFKA_CAPABILITIES)
.clientId(clientId))
.build();
@@ -1199,7 +1211,7 @@ private void onOffsetFetched(
Array32FW partitions,
KafkaOffsetFetchStream kafkaOffsetFetchStream)
{
- boolean initProducer = !partitions.anyMatch(p -> p.metadata().length() > 0);
+ boolean initProducer = isSetCleanStart(sessionFlags) || !partitions.anyMatch(p -> p.metadata().length() > 0);
partitions.forEach(partition ->
{
@@ -1290,7 +1302,7 @@ private void onOffsetCommitOpened(
initializablePartitions.forEach(kp ->
{
final long partitionKey = partitionKey(kp.topic, kp.partitionId);
- final KafkaOffsetMetadata metadata = new KafkaOffsetMetadata(producerId, producerEpoch);
+ final KafkaOffsetMetadata metadata = new KafkaOffsetMetadata(kp.topic, producerId, producerEpoch);
this.metadata.offsets.put(partitionKey, metadata);
Flyweight initialOffsetCommit = kafkaDataExRW
.wrap(extBuffer, 0, extBuffer.capacity())
@@ -1431,7 +1443,7 @@ private void doCreateSessionStream(
.flags(sessionFlags)
.expiry((int) TimeUnit.MILLISECONDS.toSeconds(sessionExpiryMillis))
.subscribeQosMax(MQTT_KAFKA_MAX_QOS)
- .publishQosMax(MQTT_KAFKA_MAX_QOS)
+ .publishQosMax(publishQosMax)
.capabilities(MQTT_KAFKA_CAPABILITIES)
.clientId(clientId);
@@ -2914,6 +2926,7 @@ private void onKafkaBegin(
.flags(delegate.sessionFlags)
.expiry((int) TimeUnit.MILLISECONDS.toSeconds(delegate.sessionExpiryMillis))
.subscribeQosMax(MQTT_KAFKA_MAX_QOS)
+ .publishQosMax(delegate.publishQosMax)
.capabilities(MQTT_KAFKA_CAPABILITIES)
.clientId(delegate.clientId))
.build();
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 852a720d26..ebbfc1d362 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
@@ -855,7 +855,7 @@ private KafkaMessagesBootstrap(
{
this.originId = originId;
this.routedId = route.id;
- this.topic = route.messages;
+ this.topic = route.with.resolveMessages();
this.serverRef = serverRef;
this.initialId = supplyInitialId.applyAsLong(routedId);
this.replyId = supplyReplyId.applyAsLong(initialId);
@@ -1118,7 +1118,7 @@ private KafkaMessagesProxy(
{
this.originId = originId;
this.routedId = route.id;
- this.topic = route.messages;
+ this.topic = route.with.resolveMessages();
this.topicKey = System.identityHashCode(topic.asString().intern());
this.routeConfig = route;
this.mqtt = mqtt;
@@ -2072,7 +2072,7 @@ private void onKafkaData(
if (((filters >> i) & 1) == 1)
{
long subscriptionId = mqtt.retainedSubscriptionIds.get(i);
- if (mqtt.retainAsPublished.getOrDefault(subscriptionId, false))
+ if (mqtt.retainAsPublished.getOrDefault(subscriptionId, Boolean.FALSE))
{
flag |= RETAIN_FLAG;
}
diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfigurationTest.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfigurationTest.java
index fccc240c61..fc272acd19 100644
--- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfigurationTest.java
+++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/MqttKafkaConfigurationTest.java
@@ -19,6 +19,7 @@
import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.BOOTSTRAP_STREAM_RECONNECT_DELAY;
import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.INSTANCE_ID;
import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.LIFETIME_ID;
+import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.PUBLISH_QOS_MAX;
import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.SESSION_ID;
import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.TIME;
import static io.aklivity.zilla.runtime.binding.mqtt.kafka.internal.MqttKafkaConfiguration.WILL_AVAILABLE;
@@ -35,7 +36,7 @@ public class MqttKafkaConfigurationTest
public static final String WILL_STREAM_RECONNECT_DELAY_NAME = "zilla.binding.mqtt.kafka.will.stream.reconnect";
public static final String BOOTSTRAP_AVAILABLE_NAME = "zilla.binding.mqtt.kafka.bootstrap.available";
public static final String BOOTSTRAP_STREAM_RECONNECT_DELAY_NAME = "zilla.binding.mqtt.kafka.bootstrap.stream.reconnect";
- public static final String PUBLISH_MAX_QOS_NAME = "zilla.binding.mqtt.kafka.publish.max.qos";
+ public static final String PUBLISH_MAX_QOS_NAME = "zilla.binding.mqtt.kafka.publish.qos.max";
public static final String SESSION_ID_NAME = "zilla.binding.mqtt.kafka.session.id";
public static final String WILL_ID_NAME = "zilla.binding.mqtt.kafka.will.id";
public static final String LIFETIME_ID_NAME = "zilla.binding.mqtt.kafka.lifetime.id";
@@ -53,5 +54,6 @@ public void shouldVerifyConstants()
assertEquals(WILL_ID.name(), WILL_ID_NAME);
assertEquals(LIFETIME_ID.name(), LIFETIME_ID_NAME);
assertEquals(INSTANCE_ID.name(), INSTANCE_ID_NAME);
+ assertEquals(PUBLISH_QOS_MAX.name(), PUBLISH_MAX_QOS_NAME);
}
}
diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishProxyIT.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishProxyIT.java
index 6e169835a7..d7e6a61c2a 100644
--- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishProxyIT.java
+++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaPublishProxyIT.java
@@ -168,6 +168,17 @@ public void shouldSendOneMessageWithChangedTopicName() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("proxy.when.publish.topic.with.messages.params.yaml")
+ @Configure(name = WILL_AVAILABLE_NAME, value = "false")
+ @Specification({
+ "${mqtt}/publish.one.message/client",
+ "${kafka}/publish.one.message.resolve.topic.params/server"})
+ public void shouldSendOneMessageWithResolvingParams() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("proxy.when.publish.topic.with.messages.yaml")
@Configure(name = WILL_AVAILABLE_NAME, value = "false")
@@ -179,6 +190,28 @@ public void shouldSendUsingTopicSpace() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("proxy.when.publish.topic.with.messages.params.yaml")
+ @Configure(name = WILL_AVAILABLE_NAME, value = "false")
+ @Specification({
+ "${mqtt}/publish.topic.space.with.params/client",
+ "${kafka}/publish.topic.space.with.params/server"})
+ public void shouldSendUsingTopicSpaceWithParams() throws Exception
+ {
+ k3po.finish();
+ }
+
+ @Test
+ @Configuration("proxy.when.publish.topic.with.messages.params.yaml")
+ @Configure(name = WILL_AVAILABLE_NAME, value = "false")
+ @Specification({
+ "${mqtt}/publish.reject.qos2/client",
+ "${kafka}/publish.reject.qos2/server"})
+ public void shouldRejectPublishWhenTopicSpaceWithParams() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("proxy.when.client.topic.space.yaml")
@Configure(name = WILL_AVAILABLE_NAME, value = "false")
diff --git a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeProxyIT.java b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeProxyIT.java
index 62e2ac00fc..e109973413 100644
--- a/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeProxyIT.java
+++ b/runtime/binding-mqtt-kafka/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/kafka/internal/stream/MqttKafkaSubscribeProxyIT.java
@@ -174,6 +174,17 @@ public void shouldReceiveOneMessageWithChangedTopicName() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("proxy.when.subscribe.topic.with.messages.params.yaml")
+ @Configure(name = WILL_AVAILABLE_NAME, value = "false")
+ @Specification({
+ "${mqtt}/subscribe.one.message/client",
+ "${kafka}/subscribe.one.message.resolve.topic.params/server"})
+ public void shouldReceiveOneMessageWithResolvingParams() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("proxy.when.subscribe.topic.with.messages.yaml")
@Configure(name = WILL_AVAILABLE_NAME, value = "false")
diff --git a/runtime/binding-mqtt/pom.xml b/runtime/binding-mqtt/pom.xml
index f0e80fa1d0..7d83a51e93 100644
--- a/runtime/binding-mqtt/pom.xml
+++ b/runtime/binding-mqtt/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
@@ -26,7 +26,7 @@
11
11
- 0.69
+ 0.88
3
diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventContext.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventContext.java
new file mode 100644
index 0000000000..89ad74c645
--- /dev/null
+++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventContext.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.mqtt.internal;
+
+import static io.aklivity.zilla.runtime.binding.mqtt.internal.types.event.MqttEventType.CLIENT_CONNECTED;
+
+import java.nio.ByteBuffer;
+import java.time.Clock;
+
+import org.agrona.concurrent.AtomicBuffer;
+import org.agrona.concurrent.UnsafeBuffer;
+
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.event.EventFW;
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.event.MqttEventExFW;
+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 MqttEventContext
+{
+ private static final int EVENT_BUFFER_CAPACITY = 2048;
+
+ private final AtomicBuffer eventBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final AtomicBuffer extensionBuffer = new UnsafeBuffer(ByteBuffer.allocate(EVENT_BUFFER_CAPACITY));
+ private final EventFW.Builder eventRW = new EventFW.Builder();
+ private final MqttEventExFW.Builder mqttEventExRW = new MqttEventExFW.Builder();
+ private final int mqttTypeId;
+ private final int clientConnectedEventId;
+ private final MessageConsumer eventWriter;
+ private final Clock clock;
+
+ public MqttEventContext(
+ EngineContext context)
+ {
+ this.mqttTypeId = context.supplyTypeId(MqttBinding.NAME);
+ this.clientConnectedEventId = context.supplyEventId("binding.mqtt.client.connected");
+ this.eventWriter = context.supplyEventWriter();
+ this.clock = context.clock();
+ }
+
+ public void onClientConnected(
+ long traceId,
+ long bindingId,
+ GuardHandler guard,
+ long authorization,
+ String clientId)
+ {
+ String identity = guard == null ? null : guard.identity(authorization);
+ MqttEventExFW extension = mqttEventExRW
+ .wrap(extensionBuffer, 0, extensionBuffer.capacity())
+ .clientConnected(e -> e
+ .typeId(CLIENT_CONNECTED.value())
+ .identity(identity)
+ .clientId(clientId)
+ )
+ .build();
+ EventFW event = eventRW
+ .wrap(eventBuffer, 0, eventBuffer.capacity())
+ .id(clientConnectedEventId)
+ .timestamp(clock.millis())
+ .traceId(traceId)
+ .namespacedId(bindingId)
+ .extension(extension.buffer(), extension.offset(), extension.limit())
+ .build();
+ eventWriter.accept(mqttTypeId, event.buffer(), event.offset(), event.limit());
+ }
+}
diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventFormatter.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventFormatter.java
new file mode 100644
index 0000000000..ba102af34c
--- /dev/null
+++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventFormatter.java
@@ -0,0 +1,73 @@
+/*
+ * 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.mqtt.internal;
+
+import org.agrona.DirectBuffer;
+
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.StringFW;
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.event.EventFW;
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.event.MqttClientConnectedExFW;
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.event.MqttEventExFW;
+import io.aklivity.zilla.runtime.engine.Configuration;
+import io.aklivity.zilla.runtime.engine.event.EventFormatterSpi;
+
+public final class MqttEventFormatter implements EventFormatterSpi
+{
+ private static final String CLIENT_CONNECTED_FORMAT = "CLIENT_CONNECTED %s %s";
+
+ private final EventFW eventRO = new EventFW();
+ private final MqttEventExFW mqttEventExRO = new MqttEventExFW();
+
+ MqttEventFormatter(
+ Configuration config)
+ {
+ }
+
+ public String format(
+ DirectBuffer buffer,
+ int index,
+ int length)
+ {
+ final EventFW event = eventRO.wrap(buffer, index, index + length);
+ final MqttEventExFW extension = mqttEventExRO
+ .wrap(event.extension().buffer(), event.extension().offset(), event.extension().limit());
+ String result = null;
+ switch (extension.kind())
+ {
+ case CLIENT_CONNECTED:
+ {
+ MqttClientConnectedExFW ex = extension.clientConnected();
+ result = String.format(CLIENT_CONNECTED_FORMAT, identity(ex.identity()), asString(ex.clientId()));
+ break;
+ }
+ }
+ return result;
+ }
+
+ private static String asString(
+ StringFW stringFW)
+ {
+ String s = stringFW.asString();
+ return s == null ? "" : s;
+ }
+
+ private static String identity(
+ StringFW identity)
+ {
+ int length = identity.length();
+ return length <= 0 ? "-" : identity.asString();
+ }
+}
diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventFormatterFactory.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventFormatterFactory.java
new file mode 100644
index 0000000000..beab5092cc
--- /dev/null
+++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/MqttEventFormatterFactory.java
@@ -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.
+ */
+package io.aklivity.zilla.runtime.binding.mqtt.internal;
+
+import io.aklivity.zilla.runtime.engine.Configuration;
+import io.aklivity.zilla.runtime.engine.event.EventFormatterFactorySpi;
+
+public final class MqttEventFormatterFactory implements EventFormatterFactorySpi
+{
+ @Override
+ public MqttEventFormatter create(
+ Configuration config)
+ {
+ return new MqttEventFormatter(config);
+ }
+
+ @Override
+ public String type()
+ {
+ return MqttBinding.NAME;
+ }
+}
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 d97dce6ade..c20584064b 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
@@ -51,7 +51,8 @@ public final class MqttBindingConfig
public final KindConfig kind;
public final MqttOptionsConfig options;
public final List routes;
- public final Function credentials;
+ public final Function resolveCredentials;
+ public final Function injectCredentials;
public final Map topics;
public final List versions;
public final ToLongFunction resolveId;
@@ -67,8 +68,10 @@ public MqttBindingConfig(
this.routes = binding.routes.stream().map(MqttRouteConfig::new).collect(toList());
this.options = (MqttOptionsConfig) binding.options;
this.resolveId = binding.resolveId;
- this.credentials = options != null && options.authorization != null ?
+ this.resolveCredentials = options != null && options.authorization != null ?
asAccessor(options.authorization.credentials) : DEFAULT_CREDENTIALS;
+ this.injectCredentials = options != null && options.authorization != null ?
+ asInjector(options.authorization.credentials) : DEFAULT_CREDENTIALS;
this.topics = new HashMap<>();
if (options != null && options.topics != null)
{
@@ -175,9 +178,14 @@ public ModelConfig supplyUserPropertyModelConfig(
return config;
}
- public Function credentials()
+ public Function resolveCredentials()
{
- return credentials;
+ return resolveCredentials;
+ }
+
+ public Function injectCredentials()
+ {
+ return injectCredentials;
}
public MqttConnectProperty authField()
@@ -215,7 +223,7 @@ private Function asAccessor(
Pattern.compile(config.pattern.replace("{credentials}", "(?[^\\s]+)"))
.matcher("");
- accessor = orElseIfNull(accessor, connect ->
+ accessor = connect ->
{
String result = null;
if (connect != null && connectMatch.reset(connect).matches())
@@ -223,21 +231,34 @@ private Function asAccessor(
result = connectMatch.group("credentials");
}
return result;
- });
+ };
}
return accessor;
}
- private static Function orElseIfNull(
- Function first,
- Function second)
+ private Function asInjector(
+ MqttCredentialsConfig credentials)
{
- return x ->
+ Function injector = DEFAULT_CREDENTIALS;
+ List connectPatterns = credentials.connect;
+
+ if (connectPatterns != null && !connectPatterns.isEmpty())
{
- String result = first.apply(x);
- return result != null ? result : second.apply(x);
- };
+ MqttPatternConfig config = connectPatterns.get(0);
+
+ injector = connect ->
+ {
+ String result = null;
+ if (connect != null)
+ {
+ result = config.pattern.replace("{credentials}", connect);
+ }
+ return result;
+ };
+ }
+
+ return injector;
}
private static class TopicValidator
diff --git a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttClientFactory.java b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttClientFactory.java
index 69e6367e3c..d979b67d0b 100644
--- a/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttClientFactory.java
+++ b/runtime/binding-mqtt/src/main/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/MqttClientFactory.java
@@ -77,6 +77,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.LongFunction;
import java.util.function.LongSupplier;
import java.util.function.LongUnaryOperator;
@@ -90,6 +91,7 @@
import org.agrona.collections.ObjectHashSet;
import org.agrona.concurrent.UnsafeBuffer;
+import io.aklivity.zilla.runtime.binding.mqtt.config.MqttPatternConfig.MqttConnectProperty;
import io.aklivity.zilla.runtime.binding.mqtt.internal.MqttBinding;
import io.aklivity.zilla.runtime.binding.mqtt.internal.MqttConfiguration;
import io.aklivity.zilla.runtime.binding.mqtt.internal.config.MqttBindingConfig;
@@ -107,6 +109,7 @@
import io.aklivity.zilla.runtime.binding.mqtt.internal.types.OctetsFW;
import io.aklivity.zilla.runtime.binding.mqtt.internal.types.String16FW;
import io.aklivity.zilla.runtime.binding.mqtt.internal.types.Varuint32FW;
+import io.aklivity.zilla.runtime.binding.mqtt.internal.types.codec.BinaryFW;
import io.aklivity.zilla.runtime.binding.mqtt.internal.types.codec.MqttConnackV5FW;
import io.aklivity.zilla.runtime.binding.mqtt.internal.types.codec.MqttConnectFW;
import io.aklivity.zilla.runtime.binding.mqtt.internal.types.codec.MqttConnectV5FW;
@@ -156,6 +159,7 @@
import io.aklivity.zilla.runtime.engine.buffer.BufferPool;
import io.aklivity.zilla.runtime.engine.concurrent.Signaler;
import io.aklivity.zilla.runtime.engine.config.BindingConfig;
+import io.aklivity.zilla.runtime.engine.guard.GuardHandler;
public final class MqttClientFactory implements MqttStreamFactory
{
@@ -177,6 +181,9 @@ public final class MqttClientFactory implements MqttStreamFactory
private static final int RETAIN_AS_PUBLISHED_MASK = 0b0000_1000;
private static final int RETAIN_HANDLING_MASK = 0b0011_0000;
+ private static final int USERNAME_MASK = 0b1000_0000;
+ private static final int PASSWORD_MASK = 0b0100_0000;
+
private static final int WILL_FLAG_MASK = 0b0000_0100;
private static final int WILL_QOS_MASK = 0b0001_1000;
private static final int WILL_RETAIN_MASK = 0b0010_0000;
@@ -287,6 +294,8 @@ public final class MqttClientFactory implements MqttStreamFactory
new Array32FW.Builder<>(new MqttUserPropertyFW.Builder(), new MqttUserPropertyFW());
private final Array32FW.Builder subscriptionIdsRW =
new Array32FW.Builder<>(new Varuint32FW.Builder(), new Varuint32FW());
+ private final BinaryFW.Builder passwordRW = new BinaryFW.Builder();
+
private final MqttClientDecoder decodeInitialType = this::decodeInitialType;
private final MqttClientDecoder decodePacketType = this::decodePacketType;
private final MqttClientDecoder decodeConnack = this::decodeConnack;
@@ -327,7 +336,8 @@ public final class MqttClientFactory implements MqttStreamFactory
private final MutableDirectBuffer propertyBuffer;
private final MutableDirectBuffer userPropertiesBuffer;
private final MutableDirectBuffer subscriptionIdsBuffer;
- private final MutableDirectBuffer willMessageBuffer;
+ private final MutableDirectBuffer connectPayloadBuffer;
+ private final MutableDirectBuffer passwordBuffer;
private final MutableDirectBuffer willPropertyBuffer;
private final ByteBuffer charsetBuffer;
@@ -347,9 +357,10 @@ public final class MqttClientFactory implements MqttStreamFactory
private final long publishTimeoutMillis;
private final long connackTimeoutMillis;
private final int encodeBudgetMax;
-
private final CharsetDecoder utf8Decoder;
+ private final EngineContext context;
+
public MqttClientFactory(
MqttConfiguration config,
EngineContext context)
@@ -363,11 +374,13 @@ public MqttClientFactory(
this.subscriptionIdsBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]);
this.payloadBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]);
this.charsetBuffer = ByteBuffer.wrap(new byte[writeBuffer.capacity()]);
- this.willMessageBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]);
+ this.connectPayloadBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]);
+ this.passwordBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]);
this.willPropertyBuffer = new UnsafeBuffer(new byte[writeBuffer.capacity()]);
this.bufferPool = context.bufferPool();
this.creditor = context.creditor();
this.signaler = context.signaler();
+ this.context = context;
this.droppedHandler = context.droppedFrameHandler();
this.streamFactory = context.streamFactory();
this.supplyDebitor = context::supplyDebitor;
@@ -389,7 +402,7 @@ public MqttClientFactory(
public void attach(
BindingConfig binding)
{
- MqttBindingConfig mqttBinding = new MqttBindingConfig(binding, null);
+ MqttBindingConfig mqttBinding = new MqttBindingConfig(binding, context);
bindings.put(binding.id, mqttBinding);
}
@@ -434,7 +447,8 @@ public MessageConsumer newStream(
final MqttBeginExFW mqttBeginEx = extension.get(mqttBeginExRO::tryWrap);
final int kind = mqttBeginEx.kind();
- MqttClient client = resolveClient(routedId, resolvedId, supplyInitialId.applyAsLong(resolvedId), affinity, kind);
+ MqttClient client = resolveClient(routedId, resolvedId, supplyInitialId.applyAsLong(resolvedId), affinity, kind,
+ binding.guard, binding.injectCredentials(), binding.authField());
switch (kind)
{
case MqttBeginExFW.KIND_SESSION:
@@ -460,10 +474,14 @@ private MqttClient resolveClient(
long resolvedId,
long initialId,
long affinity,
- int kind)
+ int kind,
+ GuardHandler guard,
+ Function credentials,
+ MqttConnectProperty authField)
{
return kind == MqttBeginExFW.KIND_SESSION ? clients.computeIfAbsent(affinity,
- s -> new MqttClient(routedId, resolvedId, initialId, maximumPacketSize)) : clients.get(affinity);
+ s -> new MqttClient(routedId, resolvedId, initialId, maximumPacketSize, guard, credentials, authField)) :
+ clients.get(affinity);
}
private MessageConsumer newStream(
@@ -1199,6 +1217,9 @@ private final class MqttClient
private final Int2ObjectHashMap subscribeStreams;
private final Int2ObjectHashMap topicAliases;
private final long encodeBudgetId;
+ private final GuardHandler guard;
+ private final Function credentials;
+ private final MqttConnectProperty authField;
private long budgetId;
private int state;
@@ -1262,7 +1283,10 @@ private MqttClient(
long originId,
long routedId,
long initialId,
- int maximumPacketSize)
+ int maximumPacketSize,
+ GuardHandler guard,
+ Function credentials,
+ MqttConnectProperty authField)
{
this.originId = originId;
this.routedId = routedId;
@@ -1275,6 +1299,9 @@ private MqttClient(
this.topicAliases = new Int2ObjectHashMap<>();
this.maximumPacketSize = maximumPacketSize;
this.packetIdCounter = new AtomicInteger();
+ this.guard = guard;
+ this.credentials = credentials;
+ this.authField = authField;
}
private void onNetwork(
@@ -2248,6 +2275,7 @@ private void doEncodeConnect(
propertiesSize = mqttProperty.limit();
}
MqttWillV5FW will = null;
+ int connectPayloadProgress = 0;
if (willMessage != null)
{
final int expiryInterval = willMessage.expiryInterval();
@@ -2307,14 +2335,38 @@ private void doEncodeConnect(
willPropertiesSize.set(mqttWillPropertyRW.limit());
});
- will = willMessageRW.wrap(willMessageBuffer, 0, willMessageBuffer.capacity())
+ will = willMessageRW.wrap(connectPayloadBuffer, 0, connectPayloadBuffer.capacity())
.properties(p -> p.length(willPropertiesSize.get())
.value(willPropertyBuffer, 0, willPropertiesSize.get()))
.topic(willMessage.topic())
.payloadSize(willMessage.payloadSize())
.build();
- willMessageBuffer.putBytes(will.limit(), willPayload.buffer(), willPayload.offset(), willPayload.limit());
+ connectPayloadBuffer.putBytes(will.limit(), willPayload.buffer(), willPayload.offset(), willPayload.limit());
+ connectPayloadProgress = willPayload.limit();
+ }
+
+ int credentialsSize = 0;
+ if (guard != null)
+ {
+ Flyweight credentials = EMPTY_OCTETS;
+ String credentials0 = this.credentials.apply(guard.credentials(authorization));
+ if (this.authField.equals(MqttConnectProperty.USERNAME))
+ {
+ flags |= USERNAME_MASK;
+ credentials = new String16FW(credentials0, BIG_ENDIAN);
+ credentialsSize = credentials.sizeof();
+ }
+ else if (this.authField.equals(MqttConnectProperty.PASSWORD))
+ {
+ flags |= PASSWORD_MASK;
+ credentials = passwordRW.wrap(passwordBuffer, 0, passwordBuffer.capacity())
+ .bytes(b -> b.set(credentials0.getBytes(StandardCharsets.UTF_8))).build();
+ credentialsSize = credentials.sizeof();
+ }
+
+ connectPayloadBuffer
+ .putBytes(connectPayloadProgress, credentials.buffer(), credentials.offset(), credentials.limit());
}
final int propertiesSize0 = propertiesSize;
@@ -2324,7 +2376,7 @@ private void doEncodeConnect(
final MqttConnectV5FW connect =
mqttConnectV5RW.wrap(writeBuffer, FIELD_OFFSET_PAYLOAD, writeBuffer.capacity())
.typeAndFlags(0x10)
- .remainingLength(11 + propertiesSize0 + clientId.length() + 2 + willSize)
+ .remainingLength(11 + propertiesSize0 + clientId.length() + 2 + willSize + credentialsSize)
.protocolName(MQTT_PROTOCOL_NAME)
.protocolVersion(MQTT_PROTOCOL_VERSION)
.flags(flags)
@@ -2335,9 +2387,9 @@ private void doEncodeConnect(
.build();
doNetworkData(traceId, authorization, 0L, connect);
- if (will != null)
+ if (will != null || guard != null)
{
- doNetworkData(traceId, authorization, 0L, willMessageBuffer, 0, willSize);
+ doNetworkData(traceId, authorization, 0L, connectPayloadBuffer, 0, willSize + credentialsSize);
}
}
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 ff18bfa0d4..c2fe91c9a2 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
@@ -112,6 +112,7 @@
import io.aklivity.zilla.runtime.binding.mqtt.config.MqttPatternConfig.MqttConnectProperty;
import io.aklivity.zilla.runtime.binding.mqtt.internal.MqttBinding;
import io.aklivity.zilla.runtime.binding.mqtt.internal.MqttConfiguration;
+import io.aklivity.zilla.runtime.binding.mqtt.internal.MqttEventContext;
import io.aklivity.zilla.runtime.binding.mqtt.internal.MqttValidator;
import io.aklivity.zilla.runtime.binding.mqtt.internal.config.MqttBindingConfig;
import io.aklivity.zilla.runtime.binding.mqtt.internal.config.MqttRouteConfig;
@@ -204,7 +205,7 @@ public final class MqttServerFactory implements MqttStreamFactory
private static final String16FW MQTT_PROTOCOL_NAME = new String16FW("MQTT", BIG_ENDIAN);
public static final int MQTT_PROTOCOL_VERSION_5 = 5;
public static final int MQTT_PROTOCOL_VERSION_4 = 4;
- private static final int MAXIMUM_CLIENT_ID_LENGTH = 36;
+ private static final int MAXIMUM_CLIENT_ID_LENGTH = 256;
private static final int CONNECT_FIXED_HEADER = 0b0001_0000;
private static final int SUBSCRIBE_FIXED_HEADER = 0b1000_0010;
private static final int UNSUBSCRIBE_FIXED_HEADER = 0b1010_0010;
@@ -406,7 +407,6 @@ public final class MqttServerFactory implements MqttStreamFactory
private final Map decodersByPacketTypeV4;
private final Map decodersByPacketTypeV5;
private final IntSupplier supplySubscriptionId;
- private final MqttQoS publishQosMax;
private final EngineContext context;
private int maximumPacketSize = Integer.MAX_VALUE;
@@ -481,6 +481,9 @@ public final class MqttServerFactory implements MqttStreamFactory
private final MqttValidator validator;
private final CharsetDecoder utf8Decoder;
private final Function supplyValidator;
+ private final MqttEventContext events;
+
+ private MqttQoS publishQosMax;
public MqttServerFactory(
MqttConfiguration config,
@@ -528,6 +531,7 @@ public MqttServerFactory(
this.decodePacketTypeByVersion.put(MQTT_PROTOCOL_VERSION_4, this::decodePacketTypeV4);
this.decodePacketTypeByVersion.put(MQTT_PROTOCOL_VERSION_5, this::decodePacketTypeV5);
this.supplyValidator = context::supplyValidator;
+ this.events = new MqttEventContext(context);
}
@Override
@@ -574,7 +578,7 @@ public MessageConsumer newStream(
affinity,
binding.versions,
binding.guard,
- binding.credentials(),
+ binding.resolveCredentials(),
binding.authField())::onNetwork;
}
return newStream;
@@ -972,7 +976,7 @@ private int decodePacketTypeV5(
server.onDecodeError(traceId, authorization, PACKET_TOO_LARGE);
server.decoder = decodeIgnoreAll;
}
- else if (length >= 0)
+ else if (limit - packet.limit() >= length || server.decodeBudget() == 0)
{
server.decodeablePacketBytes = packet.sizeof() + length;
server.decoder = decoder;
@@ -1251,6 +1255,13 @@ private int decodePublishV4(
String16FW topicName;
int publishLimit;
int packetId = 0;
+
+ if (qos > publishQosMax.value())
+ {
+ reasonCode = QOS_NOT_SUPPORTED;
+ break decode;
+ }
+
if (qos > 0)
{
final MqttPublishQosV4FW publish =
@@ -1355,6 +1366,13 @@ private int decodePublishV5(
MqttPropertiesFW properties;
int publishLimit;
int packetId = 0;
+
+ if (qos > publishQosMax.value())
+ {
+ reasonCode = QOS_NOT_SUPPORTED;
+ break decode;
+ }
+
if (qos > 0)
{
final MqttPublishQosV5FW publish =
@@ -3175,11 +3193,7 @@ private void onDecodePublish(
{
int reasonCode = SUCCESS;
- if (qos > subscribeQosMax)
- {
- reasonCode = QOS_NOT_SUPPORTED;
- }
- else if (retained && !retainAvailable(capabilities))
+ if (retained && !retainAvailable(capabilities))
{
reasonCode = RETAIN_NOT_SUPPORTED;
}
@@ -4436,6 +4450,10 @@ private void doEncodeConnack(
String16FW reason,
int version)
{
+ if (reasonCode == SUCCESS)
+ {
+ events.onClientConnected(traceId, routedId, guard, authorization, clientId.asString());
+ }
switch (version)
{
@@ -4975,6 +4993,11 @@ private boolean validContent(
payload.sizeof(), ValueConsumer.NOP);
}
+ private int decodeBudget()
+ {
+ return decodeMax - (int) (decodeSeq - decodeAck);
+ }
+
private final class Subscription
{
private int id = 0;
@@ -5212,6 +5235,7 @@ private void onSessionBegin(
sessionExpiry = mqttSessionBeginEx.expiry();
capabilities = mqttSessionBeginEx.capabilities();
subscribeQosMax = mqttSessionBeginEx.subscribeQosMax();
+ publishQosMax = MqttQoS.valueOf(mqttSessionBeginEx.publishQosMax());
if (packetIds != null)
{
packetIds.forEachRemaining((IntConsumer) p -> unreleasedPacketIds.add(p));
diff --git a/runtime/binding-mqtt/src/main/moditect/module-info.java b/runtime/binding-mqtt/src/main/moditect/module-info.java
index 4cf09438f0..06d1e06d0b 100644
--- a/runtime/binding-mqtt/src/main/moditect/module-info.java
+++ b/runtime/binding-mqtt/src/main/moditect/module-info.java
@@ -27,4 +27,7 @@
provides io.aklivity.zilla.runtime.engine.config.ConditionConfigAdapterSpi
with io.aklivity.zilla.runtime.binding.mqtt.internal.config.MqttConditionConfigAdapter;
+
+ provides io.aklivity.zilla.runtime.engine.event.EventFormatterFactorySpi
+ with io.aklivity.zilla.runtime.binding.mqtt.internal.MqttEventFormatterFactory;
}
diff --git a/runtime/binding-mqtt/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.event.EventFormatterFactorySpi b/runtime/binding-mqtt/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.event.EventFormatterFactorySpi
new file mode 100644
index 0000000000..574bd0dd80
--- /dev/null
+++ b/runtime/binding-mqtt/src/main/resources/META-INF/services/io.aklivity.zilla.runtime.engine.event.EventFormatterFactorySpi
@@ -0,0 +1 @@
+io.aklivity.zilla.runtime.binding.mqtt.internal.MqttEventFormatterFactory
diff --git a/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/client/v5/ConnectionIT.java b/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/client/v5/ConnectionIT.java
index 93a333e7b0..cf0fec79b1 100644
--- a/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/client/v5/ConnectionIT.java
+++ b/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/client/v5/ConnectionIT.java
@@ -61,6 +61,26 @@ public void shouldReceiveClientSentAbort() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("client.credentials.username.yaml")
+ @Specification({
+ "${net}/connect.username.authentication.successful/server",
+ "${app}/session.connect.authorization/client"})
+ public void shouldAuthenticateUsernameAndConnect() throws Exception
+ {
+ k3po.finish();
+ }
+
+ @Test
+ @Configuration("client.credentials.password.yaml")
+ @Specification({
+ "${net}/connect.password.authentication.successful/server",
+ "${app}/session.connect.authorization/client"})
+ public void shouldAuthenticatePasswordAndConnect() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("client.yaml")
@Specification({
diff --git a/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v4/ConnectionIT.java b/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v4/ConnectionIT.java
index 7beb8dbe69..3fc6db15b0 100644
--- a/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v4/ConnectionIT.java
+++ b/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v4/ConnectionIT.java
@@ -68,6 +68,16 @@ public void shouldConnect() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("server.log.event.yaml")
+ @Specification({
+ "${net}/connect.successful/client",
+ "${app}/session.connect/server"})
+ public void shouldConnectAndLog() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("server.credentials.username.yaml")
@Specification({
@@ -126,6 +136,15 @@ public void shouldRejectMissingClientId() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${net}/connect.reject.exceeding.max.client.id/client"})
+ public void shouldRejectExceedingClientIdMax() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("server.yaml")
@Specification({
diff --git a/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v5/ConnectionIT.java b/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v5/ConnectionIT.java
index 09d0a4965e..7d22e40ed3 100644
--- a/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v5/ConnectionIT.java
+++ b/runtime/binding-mqtt/src/test/java/io/aklivity/zilla/runtime/binding/mqtt/internal/stream/server/v5/ConnectionIT.java
@@ -66,6 +66,16 @@ public void shouldConnect() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("server.log.event.yaml")
+ @Specification({
+ "${net}/connect.successful/client",
+ "${app}/session.connect/server"})
+ public void shouldConnectAndLog() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("server.credentials.username.yaml")
@Specification({
@@ -124,6 +134,15 @@ public void shouldRejectMissingClientId() throws Exception
k3po.finish();
}
+ @Test
+ @Configuration("server.yaml")
+ @Specification({
+ "${net}/connect.reject.exceeding.max.client.id/client"})
+ public void shouldRejectExceedingClientIdMax() throws Exception
+ {
+ k3po.finish();
+ }
+
@Test
@Configuration("server.yaml")
@Specification({
diff --git a/runtime/binding-openapi-asyncapi/pom.xml b/runtime/binding-openapi-asyncapi/pom.xml
index 21fd07e4e8..9951cd1944 100644
--- a/runtime/binding-openapi-asyncapi/pom.xml
+++ b/runtime/binding-openapi-asyncapi/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncNamespaceGenerator.java b/runtime/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncNamespaceGenerator.java
index b72459bc66..dbd0a02ec3 100644
--- a/runtime/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncNamespaceGenerator.java
+++ b/runtime/binding-openapi-asyncapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/config/OpenapiAsyncNamespaceGenerator.java
@@ -145,24 +145,102 @@ private void computeRoutes(
condition.operationId : path.methods().get(method).operationId;
final OpenapiOperation openapiOperation = path.methods().get(method);
- final Optional asyncapiOperation = asyncapi.operations.entrySet().stream()
- .filter(f -> f.getKey().equals(operationId))
- .map(Map.Entry::getValue)
- .findFirst();
- final List paramNames = findParams(item);
-
- asyncapiOperation.ifPresent(operation -> binding
- .route()
- .exit(qname)
- .when(HttpKafkaConditionConfig::builder)
- .method(method)
- .path(item)
- .build()
- .inject(r -> injectHttpKafkaRouteWith(r, openapi, asyncapi, openapiOperation,
- operation, paramNames))
- .build());
+ final Optional asyncapiOperation = findAsyncOperation(
+ item, openapi, asyncapi, openapiOperation, operationId);
+
+ asyncapiOperation.ifPresent(operation ->
+ {
+ final List paramNames = findParams(item);
+
+ binding
+ .route()
+ .exit(qname)
+ .when(HttpKafkaConditionConfig::builder)
+ .method(method)
+ .path(item)
+ .build()
+ .inject(r -> injectHttpKafkaRouteWith(r, openapi, asyncapi, openapiOperation,
+ operation, paramNames))
+ .build();
+ });
+ }
+ }
+ }
+
+ private Optional findAsyncOperation(
+ String path,
+ Openapi openapi,
+ Asyncapi asyncapi,
+ OpenapiOperation openapiOperation,
+ String operationId)
+ {
+ Optional operation = findAsyncOperationByOperationId(asyncapi.operations, operationId);
+
+ if (operation.isEmpty() && isOpenapiOperationAsync(openapiOperation))
+ {
+ Optional correlatedOperationId = findOpenapiOperationIdByFormat(path, openapi);
+ if (correlatedOperationId.isPresent())
+ {
+ operation = findAsyncOperationByOperationId(asyncapi.operations, correlatedOperationId.get());
+ }
+ }
+ return operation;
+ }
+
+ private Optional findAsyncOperationByOperationId(
+ Map operations,
+ String operationId)
+ {
+ return operations.entrySet().stream()
+ .filter(f -> f.getKey().equals(operationId))
+ .map(Map.Entry::getValue)
+ .findFirst();
+ }
+
+ private Optional findOpenapiOperationIdByFormat(
+ String format,
+ Openapi openapi)
+ {
+ String operationId = null;
+ correlated:
+ for (String item : openapi.paths.keySet())
+ {
+ if (!item.equals(format))
+ {
+ OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(item));
+ for (String method : path.methods().keySet())
+ {
+ final OpenapiOperation openapiOperation = path.methods().get(method);
+ boolean formatMatched = openapiOperation.responses.entrySet().stream()
+ .anyMatch(o ->
+ {
+ OpenapiResponseByContentType content = o.getValue();
+ return "202".equals(o.getKey()) && content.headers.entrySet().stream()
+ .anyMatch(c -> matchFormat(format, c.getValue()));
+ });
+
+ if (formatMatched)
+ {
+ operationId = path.methods().get(method).operationId;
+ break correlated;
+ }
+ }
}
}
+
+ return Optional.ofNullable(operationId);
+ }
+
+ private boolean isOpenapiOperationAsync(
+ OpenapiOperation openapiOperation)
+ {
+ return openapiOperation.responses.entrySet().stream()
+ .anyMatch(o ->
+ {
+ OpenapiResponseByContentType content = o.getValue();
+ return "202".equals(o.getKey()) && content.headers.entrySet().stream()
+ .anyMatch(c -> hasCorrelationId(c.getValue()));
+ });
}
private List findParams(
@@ -304,6 +382,21 @@ private boolean hasCorrelationId(
return hasCorrelationId;
}
+ private boolean matchFormat(
+ String format,
+ OpenapiHeader header)
+ {
+ boolean matched = false;
+ OpenapiSchema schema = header.schema;
+ if (schema != null &&
+ schema.format != null)
+ {
+ matched = schema.format.equals(format);
+ }
+
+ return matched;
+ }
+
private NamespaceConfigBuilder injectNamespaceMetric(
NamespaceConfigBuilder namespace,
boolean hasMetrics)
diff --git a/runtime/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java b/runtime/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java
index 2c28776b51..70d72e6ca3 100644
--- a/runtime/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java
+++ b/runtime/binding-openapi-asyncapi/src/test/java/io/aklivity/zilla/runtime/binding/openapi/asyncapi/internal/streams/OpenapiAsyncapiIT.java
@@ -56,4 +56,15 @@ public void shouldCreatePet() throws Exception
{
k3po.finish();
}
+
+ @Test
+ @Configuration("proxy-async.yaml")
+ @Specification({
+ "${openapi}/async.verify.customer/client",
+ "${asyncapi}/async.verify.customer/server"
+ })
+ public void shouldVerifyCustomerAsync() throws Exception
+ {
+ k3po.finish();
+ }
}
diff --git a/runtime/binding-openapi/pom.xml b/runtime/binding-openapi/pom.xml
index 23c63cc997..dd224e1c0a 100644
--- a/runtime/binding-openapi/pom.xml
+++ b/runtime/binding-openapi/pom.xml
@@ -8,7 +8,7 @@
io.aklivity.zilla
runtime
- 0.9.79
+ 0.9.80
../pom.xml
diff --git a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java
index 1b020b55a1..27f91d386f 100644
--- a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java
+++ b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiBindingConfig.java
@@ -28,6 +28,7 @@
import java.util.function.ToLongFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
import org.agrona.AsciiSequenceView;
import org.agrona.DirectBuffer;
@@ -47,6 +48,7 @@
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.binding.openapi.internal.view.OpenapiServerView;
import io.aklivity.zilla.runtime.engine.catalog.CatalogHandler;
import io.aklivity.zilla.runtime.engine.config.BindingConfig;
import io.aklivity.zilla.runtime.engine.config.KindConfig;
@@ -119,29 +121,44 @@ public void attach(
BindingConfig binding)
{
List configs = convertToOpenapi(options.openapis);
- configs.forEach(c ->
+
+ final Map namespaceConfigs = new HashMap<>();
+ for (OpenapiSchemaConfig config : configs)
+ {
+ Openapi openapi = config.openapi;
+ final List servers =
+ namespaceGenerator.filterOpenapiServers(openapi.servers, options.openapis.stream()
+ .flatMap(o -> o.servers.stream())
+ .collect(Collectors.toList()));
+
+ servers.stream().collect(Collectors.groupingBy(OpenapiServerView::getPort)).forEach((k, v) ->
+ namespaceConfigs.computeIfAbsent(k, s -> new OpenapiNamespaceConfig()).addSpecForNamespace(v, config, openapi));
+ }
+
+ for (OpenapiNamespaceConfig namespaceConfig : namespaceConfigs.values())
{
- Openapi openapi = c.openapi;
- final NamespaceConfig composite = namespaceGenerator.generate(binding, openapi);
+ final NamespaceConfig composite = namespaceGenerator.generate(binding, namespaceConfig);
composite.readURL = binding.readURL;
attach.accept(composite);
- composites.put(c.schemaId, composite);
- openapi.paths.forEach((k, v) ->
+ namespaceConfig.configs.forEach(c ->
{
- String regex = k.replaceAll("\\{[^/]+}", "[^/]+");
- regex = "^" + regex + "$";
- Pattern pattern = Pattern.compile(regex);
- paths.put(pattern.matcher(""), v);
+ composites.put(c.schemaId, composite);
+ namespaceConfig.openapis.forEach(o ->
+ o.paths.forEach((k, v) ->
+ {
+ String regex = k.replaceAll("\\{[^/]+}", "[^/]+");
+ regex = "^" + regex + "$";
+ Pattern pattern = Pattern.compile(regex);
+ paths.put(pattern.matcher(""), v);
+ })
+ );
});
- });
+ }
composites.forEach((k, v) ->
{
List http = v.bindings.stream().filter(b -> b.type.equals("http")).collect(toList());
- http.stream()
- .map(b -> b.routes)
- .flatMap(List::stream)
- .forEach(r -> resolvedIds.put(k, r.id));
+ http.forEach(b -> resolvedIds.put(k, b.id));
http.stream()
.map(b -> NamespacedId.namespaceId(b.id))
.forEach(n ->
diff --git a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientNamespaceGenerator.java b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientNamespaceGenerator.java
index af7c0344b2..04a2c043a1 100644
--- a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientNamespaceGenerator.java
+++ b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiClientNamespaceGenerator.java
@@ -19,7 +19,6 @@
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfig;
import io.aklivity.zilla.runtime.binding.http.config.HttpOptionsConfigBuilder;
@@ -44,6 +43,10 @@
import io.aklivity.zilla.runtime.engine.config.ModelConfig;
import io.aklivity.zilla.runtime.engine.config.NamespaceConfig;
import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder;
+import io.aklivity.zilla.runtime.model.core.config.StringModelConfig;
+import io.aklivity.zilla.runtime.model.core.config.StringModelConfigBuilder;
+import io.aklivity.zilla.runtime.model.core.config.StringPattern;
+import io.aklivity.zilla.runtime.model.core.internal.StringModel;
import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig;
public final class OpenapiClientNamespaceGenerator extends OpenapiNamespaceGenerator
@@ -51,19 +54,15 @@ public final class OpenapiClientNamespaceGenerator extends OpenapiNamespaceGener
@Override
public NamespaceConfig generate(
BindingConfig binding,
- Openapi openapi)
+ OpenapiNamespaceConfig namespaceConfig)
{
final OpenapiOptionsConfig options = (OpenapiOptionsConfig) binding.options;
final List metricRefs = binding.telemetryRef != null ?
binding.telemetryRef.metricRefs : emptyList();
- final List servers =
- filterOpenapiServers(
- openapi.servers, options.openapis.stream()
- .flatMap(o -> o.servers.stream())
- .collect(Collectors.toList()));
+ List servers = namespaceConfig.servers;
final int[] httpsPorts = resolvePortsForScheme("https", servers);
- final boolean secure = httpsPorts != null;
+ final boolean secure = httpsPorts.length != 0;
return NamespaceConfig.builder()
.name(String.format(binding.qname, "$composite"))
@@ -72,7 +71,7 @@ public NamespaceConfig generate(
.name("http_client0")
.type("http")
.kind(CLIENT)
- .inject(b -> this.injectHttpClientOptions(b, openapi))
+ .inject(b -> this.injectHttpClientOptions(b, namespaceConfig.openapis))
.inject(b -> this.injectMetrics(b, metricRefs, "http"))
.exit(secure ? "tls_client0" : "tcp_client0")
.build()
@@ -89,15 +88,18 @@ public NamespaceConfig generate(
private BindingConfigBuilder injectHttpClientOptions(
BindingConfigBuilder binding,
- Openapi openApi)
+ List openApis)
{
- OpenapiOperationsView operations = OpenapiOperationsView.of(openApi.paths);
- if (operations.hasResponses())
+ for (Openapi openApi : openApis)
{
- binding.
- options(HttpOptionsConfig::builder)
- .inject(options -> injectHttpClientRequests(operations, options, openApi))
- .build();
+ OpenapiOperationsView operations = OpenapiOperationsView.of(openApi.paths);
+ if (operations.hasResponses())
+ {
+ binding.
+ options(HttpOptionsConfig::builder)
+ .inject(options -> injectHttpClientRequests(operations, options, openApi))
+ .build();
+ }
}
return binding;
}
@@ -175,16 +177,33 @@ private HttpResponseConfigBuilder injectResponseHeaders(
{
String name = header.getKey();
OpenapiSchema schema = header.getValue().schema;
- String modelName = schema.format != null ? String.format("%s:%s", schema.type, schema.format) :
- schema.type;
- ModelConfig model = models.get(modelName);
- if (model != null)
+ if (schema != null)
{
- response
- .header()
+ String format = schema.format;
+ String type = schema.type;
+ ModelConfig model;
+ if (StringModel.NAME.equals(type))
+ {
+ StringModelConfigBuilder builder = StringModelConfig.builder();
+ if (format != null)
+ {
+ builder.pattern(StringPattern.of(format));
+ }
+ model = builder.build();
+ }
+ else
+ {
+ model = MODELS.get(format != null ? String.format("%s:%s", type, format) : type);
+ }
+
+ if (model != null)
+ {
+ response
+ .header()
.name(name)
.model(model)
.build();
+ }
}
}
}
diff --git a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceConfig.java b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceConfig.java
new file mode 100644
index 0000000000..eb2515997f
--- /dev/null
+++ b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceConfig.java
@@ -0,0 +1,49 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+
+import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiSchemaConfig;
+import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi;
+import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiServerView;
+
+public class OpenapiNamespaceConfig
+{
+ List openapiLabels;
+ List servers;
+ List openapis;
+ List configs;
+
+ public OpenapiNamespaceConfig()
+ {
+ openapiLabels = new ArrayList<>();
+ servers = new ArrayList<>();
+ openapis = new ArrayList<>();
+ configs = new ArrayList<>();
+ }
+
+ public void addSpecForNamespace(
+ List servers,
+ OpenapiSchemaConfig config,
+ Openapi openapi)
+ {
+ this.openapiLabels.add(config.apiLabel);
+ this.servers.addAll(servers);
+ this.configs.add(config);
+ this.openapis.add(openapi);
+ }
+}
diff --git a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceGenerator.java b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceGenerator.java
index 9ed52ad14f..bb3dca77c1 100644
--- a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceGenerator.java
+++ b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiNamespaceGenerator.java
@@ -28,7 +28,6 @@
import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiOptionsConfig;
import io.aklivity.zilla.runtime.binding.openapi.config.OpenapiServerConfig;
-import io.aklivity.zilla.runtime.binding.openapi.internal.model.Openapi;
import io.aklivity.zilla.runtime.binding.openapi.internal.model.OpenapiServer;
import io.aklivity.zilla.runtime.binding.openapi.internal.view.OpenapiServerView;
import io.aklivity.zilla.runtime.engine.config.BindingConfig;
@@ -38,9 +37,10 @@
import io.aklivity.zilla.runtime.engine.config.NamespaceConfig;
import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder;
import io.aklivity.zilla.runtime.engine.config.TelemetryRefConfigBuilder;
+import io.aklivity.zilla.runtime.model.core.config.DoubleModelConfig;
+import io.aklivity.zilla.runtime.model.core.config.FloatModelConfig;
import io.aklivity.zilla.runtime.model.core.config.Int32ModelConfig;
import io.aklivity.zilla.runtime.model.core.config.Int64ModelConfig;
-import io.aklivity.zilla.runtime.model.core.config.StringModelConfig;
public abstract class OpenapiNamespaceGenerator
{
@@ -49,18 +49,20 @@ public abstract class OpenapiNamespaceGenerator
protected static final String VERSION_LATEST = "latest";
protected static final Pattern JSON_CONTENT_TYPE = Pattern.compile("^application/(?:.+\\+)?json$");
protected static final OpenapiOptionsConfig EMPTY_OPTIONS = OpenapiOptionsConfig.builder().build();
-
- protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher("");
- protected final Map models = Map.of(
- "string", StringModelConfig.builder().build(),
+ protected static final Map MODELS = Map.of(
"integer", Int32ModelConfig.builder().build(),
"integer:int32", Int32ModelConfig.builder().build(),
- "integer:int64", Int64ModelConfig.builder().build()
+ "integer:int64", Int64ModelConfig.builder().build(),
+ "number", FloatModelConfig.builder().build(),
+ "number:float", FloatModelConfig.builder().build(),
+ "number:double", DoubleModelConfig.builder().build()
);
+ protected final Matcher jsonContentType = JSON_CONTENT_TYPE.matcher("");
+
public abstract NamespaceConfig generate(
BindingConfig binding,
- Openapi openapi);
+ OpenapiNamespaceConfig namespaceConfig);
protected NamespaceConfigBuilder injectNamespaceMetric(
NamespaceConfigBuilder namespace,
diff --git a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerNamespaceGenerator.java b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerNamespaceGenerator.java
index 4da97ae662..dea107b949 100644
--- a/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerNamespaceGenerator.java
+++ b/runtime/binding-openapi/src/main/java/io/aklivity/zilla/runtime/binding/openapi/internal/config/OpenapiServerNamespaceGenerator.java
@@ -23,7 +23,6 @@
import java.net.URI;
import java.util.List;
import java.util.Map;
-import java.util.stream.Collectors;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
@@ -58,6 +57,10 @@
import io.aklivity.zilla.runtime.engine.config.NamespaceConfig;
import io.aklivity.zilla.runtime.engine.config.NamespaceConfigBuilder;
import io.aklivity.zilla.runtime.engine.config.RouteConfigBuilder;
+import io.aklivity.zilla.runtime.model.core.config.StringModelConfig;
+import io.aklivity.zilla.runtime.model.core.config.StringModelConfigBuilder;
+import io.aklivity.zilla.runtime.model.core.config.StringPattern;
+import io.aklivity.zilla.runtime.model.core.internal.StringModel;
import io.aklivity.zilla.runtime.model.json.config.JsonModelConfig;
public final class OpenapiServerNamespaceGenerator extends OpenapiNamespaceGenerator
@@ -67,26 +70,23 @@ public final class OpenapiServerNamespaceGenerator extends OpenapiNamespaceGener
@Override
public NamespaceConfig generate(
BindingConfig binding,
- Openapi openapi)
+ OpenapiNamespaceConfig namespaceConfig)
{
final OpenapiOptionsConfig options = binding.options != null ? (OpenapiOptionsConfig) binding.options : EMPTY_OPTIONS;
final List metricRefs = binding.telemetryRef != null ?
binding.telemetryRef.metricRefs : emptyList();
- final List servers =
- filterOpenapiServers(
- openapi.servers, options.openapis.stream()
- .flatMap(o -> o.servers.stream())
- .collect(Collectors.toList()));
+ final List servers = namespaceConfig.servers;
final String qvault = String.format("%s:%s", binding.namespace, binding.vault);
+ final String namespace = String.join("+", namespaceConfig.openapiLabels);
return NamespaceConfig.builder()
- .name(String.format("%s/http", binding.qname))
- .inject(namespace -> injectNamespaceMetric(namespace, !metricRefs.isEmpty()))
- .inject(n -> injectCatalog(n, openapi))
+ .name(String.format("%s/%s", binding.qname, namespace))
+ .inject(n -> injectNamespaceMetric(n, !metricRefs.isEmpty()))
+ .inject(n -> injectCatalog(n, namespaceConfig.openapis))
.inject(n -> injectTcpServer(n, servers, options, metricRefs))
.inject(n -> injectTlsServer(n, qvault, servers, options, metricRefs))
- .inject(n -> injectHttpServer(n, binding.qname, openapi, servers, options, metricRefs))
+ .inject(n -> injectHttpServer(n, binding.qname, namespaceConfig.openapis, servers, options, metricRefs))
.build();
}
@@ -166,25 +166,28 @@ private HttpOptionsConfigBuilder injectHttpServerOptions(
private HttpOptionsConfigBuilder injectHttpServerRequests(
HttpOptionsConfigBuilder options,
- Openapi openapi)
+ List openapis)
{
- for (String pathName : openapi.paths.keySet())
+ for (Openapi openapi : openapis)
{
- OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(pathName));
- for (String methodName : path.methods().keySet())
+ for (String pathName : openapi.paths.keySet())
{
- final OpenapiOperation operation = path.methods().get(methodName);
- if (operation.requestBody != null ||
- operation.parameters != null &&
- !operation.parameters.isEmpty())
+ OpenapiPathView path = OpenapiPathView.of(openapi.paths.get(pathName));
+ for (String methodName : path.methods().keySet())
{
- options
- .request()
- .path(pathName)
- .method(HttpRequestConfig.Method.valueOf(methodName))
- .inject(request -> injectContent(request, operation, openapi))
- .inject(request -> injectParams(request, operation))
- .build();
+ 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();
+ }
}
}
}
@@ -226,12 +229,25 @@ private HttpRequestConfigBuilder